Hello World, 2023-07-11 16:32 采纳率: 100%
浏览 66
已结题

C#生产排单计算,不知使用什么算法

要对若干工作中心(都是机台)进行有限产能的生产安排,要求机台的利用率越高越好。

影响排产的因素比较多。
一、工作中心能生产的工序、工序的类别(难易程度)要考虑
二、配置的技术人员不是一对一的,一个班次通常是两个技术人员,管理若干个机台。在每个任务生产前需要调机,占用技术人员的时间,一个技术员同一时间只能调一个机台。
三、工作中心有班次安排,计算起止时间需要考虑班次
四、工作中心有维护计划,在维护计划时段内的不能排单
五、同一任务可以分拆开,安排在不同机台上同时生产
六、如果某工作中心在此之前有生产过这个产品并且是这个工序的,优先考虑,调机时间为15分钟,如果没有生产过则调机时间为20分钟
七、如果上一班次未排完则下一班次继续,连续生产的不需要调机

相关的类简化后大致如下,求Calculate过程的算法实现:

    class Test
    {
        /// <summary>
        /// 排产计划
        /// </summary>
        /// <param name="tasks">等生产的任务列表</param>
        /// <param name="date">排产日期</param>
        /// <param name="shiftCode">班次,每个工作中心(机台)可能有1~2个班次,不指定班次则全部班次都要排</param>
        /// <param name="peoples">技工人数,调机需要技工,技工不能同时调多机台</param>
        /// <returns></returns>
        public List<Plan> Calculate(List<Task> tasks, DateTime date, string shiftCode = "", int peoples = 2)
        {
            //此处写计算逻辑,要求用最短的时间完成任务,机台和人员利用率尽量高
            //当前一班次有未完成的任务时,优先安排同工作中心继续生产,并且无需调机
            //优先安排之前有生产过此产品此工序的工作中心生产,调机时间较少
            //调机需要技工,技工不能同时出现在多个工作中心,上班时间与机台班次相同
            //同一任务可以拆分到多个工作中心生产


            //工作中心、排班等资料从数据库获取,此处省略获取方法
            List<Workcenter> workcenters = new List<Workcenter>();
            List<MaintenancePlan> MaintenancePlans = new List<MaintenancePlan>();
            //其它参数及算法

            return new List<Plan>();
        }
    }
   /// <summary>
    /// 工作中心
    /// </summary>
    class Workcenter
    {
        public int ID { get; set; }
    }
    /// <summary>
    /// 工序
    /// </summary>
    class Process
    {
        public int ID { get; set; }
    }
    /// <summary>
    /// 工序类型,例如难、中、易等
    /// </summary>
    class ProcessType
    {
        public int ID { get; set; }
    }
    /// <summary>
    /// 工作中心可以生产的工序类型
    /// </summary>
    class WorkcenterProcessType
    {
        /// <summary>
        /// 工作中心ID
        /// </summary>
        public int WorkcenterID { get; set; }
        /// <summary>
        /// 工序ID
        /// </summary>
        public int ProcessID { get; set; }
        /// <summary>
        /// 工序类型ID
        /// </summary>
        public int ProcessTypeID { get; set; }
    }
    /// <summary>
    /// 班次
    /// </summary>
    class Shift
    {
        public string ShiftCode { get; set; }
    }
    /// <summary>
    /// 班次明细
    /// </summary>
    class ShiftDetails
    {
        public string ShiftCode { get; set; }
        /// <summary>
        /// 班次开始时间,分钟数,例如中午12:00为720
        /// 夜班跨天加1440分钟
        /// </summary>
        public int BeginTime { get; set; }
        public int EndTime { get; set; }
    }

    /// <summary>
    /// 维护计划,在维护计划时段内的机台不可用
    /// </summary>
    class MaintenancePlan
    {
        public int ID { get; set; }
        public int WorkcenterID { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }

    /// <summary>
    /// 生产任务
    /// </summary>
    class Task
    {
        public int ID { get; set; }
        /// <summary>
        /// 待生产的数量
        /// </summary>
        public decimal Qty { get; set; }
        public int ProcessID { get; set; }
        public int ProcessTypeID { get; set; }
        /// <summary>
        /// 前置时间,调机用时,需要技术员。技术员上班时间同机台排班
        /// 如果工作中心有生产此产品、此工序则调机时间为15分钟,否则为20分钟
        /// </summary>
        public decimal PreMinutes { get; set; }
        /// <summary>
        /// 产能
        /// </summary>
        public int Capacity { get; set; }
    }
    /// <summary>
    /// 排产结果
    /// </summary>
    class Plan
    {
        public int ID { get; set; }
        public int TaskID { get; set; }
        public int WorkcenterID { get; set; }
        public DateTime Date { get; set; }
        public string ShiftCode { get; set; }
        public decimal Qty { get; set; }
        public decimal PrevMinutes { get; set; }
        /// <summary>
        /// 开始生产的时间,包含前置的调机时间
        /// 班次日期经过的分钟数
        /// </summary>
        public int StartTime { get; set; }
        public int EndTime { get; set; }
    }
  • 写回答

8条回答 默认 最新

  • 炎热的夏季 2023-07-12 15:29
    关注

    采用chatgpt:
    在给定的情况下,你可以使用启发式算法来解决这个生产排单计算问题。启发式算法是一种基于经验和规则的搜索算法,它可以在可接受的时间内找到一个较好的解决方案。

    以下是一个可能的算法实现思路:

    1、根据任务的工序类型和工作中心的能力,对任务进行分组。将具有相同工序类型的任务归为一组,每个工作中心对应多个任务组。

    2、对每个任务组进行排序,优先安排之前有生产过此产品此工序的工作中心生产。排序可以根据工作中心的利用率、调机时间等因素进行。

    3、对于每个任务组,按照以下步骤进行排产:

    a. 检查维护计划,将在维护计划时段内的机台排除。

    b. 对于每个班次,按照以下步骤进行排产:

    i. 检查是否有前一班次未完成的任务,如果有,优先安排同工作中心继续生产,并且无需调机。

    ii. 检查技工的可用性,一个技工同一时间只能调一个机台。

    iii. 对于每个工作中心,计算该班次的可用时间段。

    iv. 在可用时间段内,选择一个工作中心进行排产,考虑调机时间和产能。

    v. 更新技工的可用时间和机台的占用情况。

    4、重复步骤3,直到所有任务组都排产完成。

    5、返回排产结果。
    以下是一个可能的示例代码,用于实现生产排单计算的启发式算法:

    class Test
    {
        public List<Plan> Calculate(List<Task> tasks, DateTime date, string shiftCode = "", int peoples = 2)
        {
            // 从数据库获取工作中心、排班等信息
            List<Workcenter> workcenters = GetWorkcenters();
            List<WorkcenterProcessType> workcenterProcessTypes = GetWorkcenterProcessTypes();
            List<MaintenancePlan> maintenancePlans = GetMaintenancePlans();
            List<ShiftDetails> shiftDetails = GetShiftDetails();
    
            // 用于存储排产结果
            List<Plan> plans = new List<Plan>();
    
            // 按照工序类型进行任务分组
            var taskGroups = tasks.GroupBy(t => t.ProcessTypeID);
    
            foreach (var group in taskGroups)
            {
                // 按照优先级排序任务组
                var sortedGroup = group.OrderByDescending(t => t.PreMinutes).ThenByDescending(t => t.Capacity).ToList();
    
                foreach (var task in sortedGroup)
                {
                    // 在指定日期和班次范围内进行排产
                    foreach (var shift in shiftDetails)
                    {
                        if (!string.IsNullOrEmpty(shiftCode) && shift.ShiftCode != shiftCode)
                            continue;
    
                        // 当前班次内的可用时间段
                        var availableTime = new TimeRange(shift.BeginTime, shift.EndTime);
    
                        // 检查维护计划,排除在维护计划时段内的机台
                        var maintenanceTimeRanges = maintenancePlans
                            .Where(m => m.WorkcenterID == task.WorkcenterID && m.StartTime.Date == date.Date)
                            .Select(m => new TimeRange(m.StartTime, m.EndTime));
    
                        availableTime = availableTime.ExcludeRanges(maintenanceTimeRanges);
    
                        // 检查前一班次是否有未完成的任务
                        var previousShift = GetPreviousShift(shift);
                        var previousTasks = plans
                            .Where(p => p.Date.Date == date.Date && p.ShiftCode == previousShift.ShiftCode && p.WorkcenterID == task.WorkcenterID)
                            .ToList();
    
                        if (previousTasks.Count > 0)
                        {
                            // 优先安排同工作中心继续生产
                            var previousTask = previousTasks.OrderByDescending(p => p.EndTime).First();
    
                            if (previousTask.EndTime < shift.BeginTime)
                            {
                                // 前一班次任务已结束,可以继续生产,无需调机
                                var plan = new Plan()
                                {
                                    TaskID = task.ID,
                                    WorkcenterID = task.WorkcenterID,
                                    Date = date,
                                    ShiftCode = shift.ShiftCode,
                                    Qty = Math.Min(task.Qty, task.Capacity),
                                    PrevMinutes = 0,
                                    StartTime = shift.BeginTime,
                                    EndTime = shift.BeginTime + task.PreMinutes + CalculateProductionTime(task, shift.BeginTime, shift.EndTime)
                                };
    
                                plans.Add(plan);
                                task.Qty -= plan.Qty;
    
                                // 更新可用时间段
                                availableTime = availableTime.ExcludeRange(plan.StartTime, plan.EndTime);
                            }
                        }
    
                        // 检查技工的可用性
                        var technicianAvailability = new Dictionary<int, TimeRange>();
                        var technicianTasks = plans
                            .Where(p => p.Date.Date == date.Date && p.ShiftCode == shift.ShiftCode)
                            .GroupBy(p => p.WorkcenterID)
                            .ToDictionary(g => g.Key, g => g.ToList());
    
                        foreach (var technicianTasksEntry in technicianTasks)
                        {
                            var technicianId = technicianTasksEntry.Key;
                            var technicianPlans = technicianTasksEntry.Value;
                            var technicianTimeRanges = technicianPlans.Select(p => new TimeRange(p.StartTime, p.EndTime));
                            var technicianAvailableTime = availableTime.ExcludeRanges(technicianTimeRanges);
                            technicianAvailability.Add(technicianId, technicianAvailableTime);
                        }
    
                        // 根据可用时间段和产能选择一个工作中心进行排产
                        var selectedWorkcenter = GetBestWorkcenter(workcenters, workcenterProcessTypes, task, availableTime, technicianAvailability, peoples);
    
                        if (selectedWorkcenter != null)
                        {
                            var plan = new Plan()
                            {
                                TaskID = task.ID,
                                WorkcenterID = selectedWorkcenter.ID,
                                Date = date,
                                ShiftCode = shift.ShiftCode,
                                Qty = Math.Min(task.Qty, task.Capacity),
                                PrevMinutes = task.PreMinutes,
                                StartTime = selectedWorkcenter.NextAvailableTime,
                                EndTime = selectedWorkcenter.NextAvailableTime + task.PreMinutes + CalculateProductionTime(task, selectedWorkcenter.NextAvailableTime, shift.EndTime)
                            };
    
                            plans.Add(plan);
                            task.Qty -= plan.Qty;
    
                            // 更新技工的可用时间段
                            var technicianId = selectedWorkcenter.TechnicianID;
                            technicianAvailability[technicianId] = technicianAvailability[technicianId].ExcludeRange(plan.StartTime, plan.EndTime);
                        }
    
                        // 如果任务还未完成且产能未用尽,继续在下一个班次排产
                        if (task.Qty > 0 && task.Qty > task.Capacity)
                            continue;
                        else
                            break;
                    }
                }
            }
    
            return plans;
        }
    
        private List<Workcenter> GetWorkcenters()
        {
            // 从数据库获取工作中心信息的实现
            // 省略实现细节,返回模拟数据
            return new List<Workcenter>
            {
                new Workcenter { ID = 1 },
                new Workcenter { ID = 2 },
                // ...
            };
        }
    
        private List<WorkcenterProcessType> GetWorkcenterProcessTypes()
        {
            // 从数据库获取工作中心可以生产的工序类型信息的实现
            // 省略实现细节,返回模拟数据
            return new List<WorkcenterProcessType>
            {
                new WorkcenterProcessType { WorkcenterID = 1, ProcessID = 1, ProcessTypeID = 1 },
                new WorkcenterProcessType { WorkcenterID = 1, ProcessID = 2, ProcessTypeID = 1 },
                new WorkcenterProcessType { WorkcenterID = 2, ProcessID = 2, ProcessTypeID = 2 },
                // ...
            };
        }
    
        private List<MaintenancePlan> GetMaintenancePlans()
        {
            // 从数据库获取维护计划信息的实现
            // 省略实现细节,返回模拟数据
            return new List<MaintenancePlan>
            {
                new MaintenancePlan { ID = 1, WorkcenterID = 1, StartTime = DateTime.Now.AddDays(-1), EndTime = DateTime.Now.AddDays(-1).AddHours(2) },
                new MaintenancePlan { ID = 2, WorkcenterID = 2, StartTime = DateTime.Now.AddDays(-1), EndTime = DateTime.Now.AddDays(-1).AddHours(4) },
                // ...
            };
        }
    
        private List<ShiftDetails> GetShiftDetails()
        {
            // 从数据库获取班次明细信息的实现
            // 省略实现细节,返回模拟数据
            return new List<ShiftDetails>
            {
                new ShiftDetails { ShiftCode = "DayShift", BeginTime = 480, EndTime = 960 },
                new ShiftDetails { ShiftCode = "NightShift", BeginTime = 1200, EndTime = 240 },
                // ...
            };
        }
    
        private ShiftDetails GetPreviousShift(ShiftDetails currentShift)
        {
            // 获取前一班次的实现
            // 省略实现细节,根据实际班次规则计算前一班次的开始时间和结束时间
            return new ShiftDetails { ShiftCode = "PreviousShift", BeginTime = currentShift.BeginTime - 1440, EndTime = currentShift.EndTime - 1440 };
        }
    
        private int CalculateProductionTime(Task task, int startTime, int endTime)
        {
            // 根据任务的产能和工作中心的利用率等因素计算生产时间的实现
            // 省略实现细节,根据实际业务需求计算生产时间
            return 0;
        }
    
        private Workcenter GetBestWorkcenter(List<Workcenter> workcenters, List<WorkcenterProcessType> workcenterProcessTypes, Task task, TimeRange availableTime, Dictionary<int, TimeRange> technicianAvailability, int peoples)
        {
            // 选择最佳的工作中心的实现
            // 省略实现细节,根据任务的调机时间、产能需求和工作中心的可用性等因素选择最佳的工作中心
            return null;
        }
    }
    
    class TimeRange
    {
        public int StartTime { get; set; }
        public int EndTime { get; set; }
    
        public TimeRange(int startTime, int endTime)
        {
            StartTime = startTime;
            EndTime = endTime;
        }
    
        public TimeRange ExcludeRange(int start, int end)
        {
            if (end <= StartTime || start >= EndTime)
            {
                return this;
            }
            else if (start <= StartTime && end >= EndTime)
            {
                return null;
            }
            else if (start <= StartTime && end < EndTime)
            {
                return new TimeRange(end, EndTime);
            }
            else if (start > StartTime && end >= EndTime)
            {
                return new TimeRange(StartTime, start);
            }
            else
            {
                return new TimeRange(StartTime, start).Union(new TimeRange(end, EndTime));
            }
        }
    
        public List<TimeRange> ExcludeRanges(IEnumerable<TimeRange> ranges)
        {
            var result = new List<TimeRange> { this };
            foreach (var range in ranges)
            {
                var newResult = new List<TimeRange>();
                foreach (var currentRange in result)
                {
                    var excludedRange = currentRange.ExcludeRange(range.StartTime, range.EndTime);
                    if (excludedRange != null)
                    {
                        newResult.Add(excludedRange);
                    }
                }
                result = newResult;
            }
            return result;
        }
    
        public TimeRange Union(TimeRange range)
        {
            return new TimeRange(Math.Min(StartTime, range.StartTime), Math.Max(EndTime, range.EndTime));
        }
    }
    

    这只是一个简化的示例代码,可能需要根据实际需求进行调整和优化。实际应用中,你可能需要根据数据库的结构和业务规则进行适当的修改和完善。另外,一些辅助函数的具体实现(例如获取工作中心、排班、维护计划等信息)需要根据你的数据源和业务逻辑进行实现。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(7条)

报告相同问题?

问题事件

  • 系统已结题 7月24日
  • 已采纳回答 7月16日
  • 创建了问题 7月11日

悬赏问题

  • ¥15 yolov8边框坐标
  • ¥15 matlab中使用gurobi时报错
  • ¥15 这个主板怎么能扩出一两个sata口
  • ¥15 不是,这到底错哪儿了😭
  • ¥15 2020长安杯与连接网探
  • ¥15 关于#matlab#的问题:在模糊控制器中选出线路信息,在simulink中根据线路信息生成速度时间目标曲线(初速度为20m/s,15秒后减为0的速度时间图像)我想问线路信息是什么
  • ¥15 banner广告展示设置多少时间不怎么会消耗用户价值
  • ¥16 mybatis的代理对象无法通过@Autowired装填
  • ¥15 可见光定位matlab仿真
  • ¥15 arduino 四自由度机械臂