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; }
    }

采用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));
    }
}

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

TechWhizKid参考GPT回答:

  • 常见的方法是使用贪心算法或优先队列。
    提供一个概念性的伪代码来解决这个问题。

这个问题可以视为一种作业调度问题(Job Scheduling Problem),其主要目标是最大化机台的使用率和最小化总的生产时间。这是一种经典的优化问题

以下是一种可能的解决方法的伪代码:

class Test
{
    // 计算方法,接收任务列表、日期、班次代码、人数作为参数,返回计划列表
    public List<Plan> Calculate(List<Task> tasks, DateTime date, string shiftCode = "", int peoples = 2)
    {
        // 从数据库获取工作中心列表和维护计划列表
        List<Workcenter> workcenters = GetWorkcentersFromDB();
        List<MaintenancePlan> MaintenancePlans = GetMaintenancePlansFromDB();

        // 根据优先级对任务进行排序,优先级可以基于多个因素进行决定
        // 例如,之前由工作中心生产的任务可以赋予更高的优先级
        tasks = SortTasksBasedOnPriority(tasks, workcenters);

        // 创建一个列表来存储生产计划
        List<Plan> plans = new List<Plan>();

        // 对于每个任务,根据工作中心的可用性和维护计划分配任务
        foreach (Task task in tasks)
        {
            // 根据可用性和维护计划找到最佳工作中心
            Workcenter workcenter = FindBestWorkcenter(task, workcenters, MaintenancePlans);

            if (workcenter != null)
            {
                // 如果找到合适的工作中心,为该任务创建一个新的计划并将其添加到计划列表中
                Plan plan = new Plan();
                plan.TaskID = task.ID;
                plan.WorkcenterID = workcenter.ID;
                // 设置计划的其余字段

                plans.Add(plan);
            }
        }

        return plans;
    }
}

建议你参照这个伪代码,结合你自己的实际情况来开发代码。

搜索背包算法、排料算法,其实思路都是差不多的,这种问题一般很难找到最优解,但是可以找到近似最优解。

这个问题属于经典的作业车间调度问题(Job Shop Scheduling Problem),属于NP难问题,因此完美的最优解很难找到,可以找到近似最优解。
可以考虑使用调度算法来进行生产排单计算。调度算法可以帮助确定最佳的工作中心和任务调度顺序,以优化机台的利用率。

Quartz任务调度

  • 你可以参考下这个问题的回答, 看看是否对你有帮助, 链接: https://ask.csdn.net/questions/7785575
  • 除此之外, 这篇博客: C语言实现八大排序算法详解及其性能之间的中的 名字已经暴露了他的算法,就是往里面插入数据,就拿我们生活中的例子来说,打扑克牌。我们往手里码牌的时候,是一张一张的码,先码一张,抓手心,不需要修改位置,因为只有一张牌,一定是有序的。再接一张,和手里的牌对比大小,调整位置,选择放在它的左边或者右边。然后接着码,又接到一张牌,拿到先和右边的牌比,比右边还大就放到最右边,如果比右边这张小呢,在和左边这张比。同样,我们这里也是这样的,首先我们默认第一个元素,一定是有序,OK吧。然后第二个,元素比较,大,放到左边,小放到右边。然后第三个元素,直到第N个,比它前一个大,继续往前找位置,直到找到对应位置了,就是有序数列了。(当然每次找位置都是在一个有序的序列中找,所以完全可以用二分查找找位置,数据大的话,二分明显快于我们一张一张比) 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 算法思想

以下是一个简单的C#代码示例,用于计算生产排单。它使用了一些基本的算法来安排任务和工作中心。

using System;  
using System.Collections.Generic;  
  
public class Test  
{  
    public List<Plan> Calculate(List<Task> tasks, DateTime date, string shiftCode = "", int peoples = 2)  
    {  
        List<Workcenter> workcenters = GetWorkcentersFromDatabase(); // 从数据库获取工作中心信息  
        List<Shift> shifts = GetShiftsFromDatabase(); // 从数据库获取班次信息  
        List<ProcessType> processTypes = GetProcessTypesFromDatabase(); // 从数据库获取工序类型信息  
        List<WorkcenterProcessType> workcenterProcessTypes = GetWorkcenterProcessTypesFromDatabase(); // 从数据库获取工作中心可以生产的工序类型信息  
  
        // 根据班次和人员限制进行排产计划计算  
        List<Plan> plans = new List<Plan>();  
        foreach (var task in tasks)  
        {  
            // 确定任务的工作中心和班次  
            Workcenter workcenter = workcenters.Find(w => w.ID == task.WorkcenterID);  
            Shift shift = shifts.Find(s => s.ShiftCode == shiftCode || shiftCode == "");  
  
            // 确定任务的工序类型和拆分方式  
            ProcessType processType = processTypes.Find(pt => pt.ID == task.ProcessTypeID);  
            List<Process> processes = new List<Process>();  
            if (task.Processes != null)  
            {  
                foreach (var p in task.Processes)  
                {  
                    Process process = new Process { ID = p.ID, Name = p.Name };  
                    processes.Add(process);  
                }  
            }  
            else  
            {  
                processes.Add(new Process { ID = task.ProcessID, Name = task.ProcessName });  
            }  
  
            // 确定工作中心可以生产的工序类型  
            List<int> workcenterProcessTypeIds = workcenterProcessTypes.FindAll(wpt => wpt.WorkcenterID == workcenter.ID && wpt.ProcessTypeID == processType.ID);  
            List<int> availableProcessTypes = new List<int>();  
            foreach (int id in workcenterProcessTypeIds)  
            {  
                ProcessType availableProcessType = processTypes.Find(pt => pt.ID == id);  
                if (availableProcessType != null)  
                {  
                    availableProcessTypes.Add(id);  
                }  
            }  
  
            // 创建排产计划对象并添加到计划列表中  
            Plan plan = new Plan { Date = date, Shift = shift, Workcenter = workcenter, Processes = processes, AvailableProcessTypes = availableProcessTypes };  
            plans.Add(plan);  
        }  
  
        return plans;  
    }  
}

#如有帮助,恭请采纳

class WorkCenter:
    def __init__(self, id, available_processes, maintenance_schedule):
        self.id = id  # 工作中心的唯一标识符
        self.available_processes = available_processes  # 工作中心能够生产的工序列表
        self.maintenance_schedule = maintenance_schedule  # 工作中心的维护计划
 class Technician:
    def __init__(self, id):
        self.id = id  # 技术人员的唯一标识符
 class Task:
    def __init__(self, id, processes):
        self.id = id  # 任务的唯一标识符
        self.processes = processes  # 任务所需的工序列表
 class Schedule:
    def __init__(self):
        self.work_centers = []  # 工作中心列表
        self.technicians = []  # 技术人员列表
        self.tasks = []  # 任务列表
     def add_work_center(self, work_center):
        self.work_centers.append(work_center)
     def add_technician(self, technician):
        self.technicians.append(technician)
     def add_task(self, task):
        self.tasks.append(task)
     def calculate_schedule(self):
        # 实现算法来计算生产安排
        # 可以根据你的具体需求和算法设计来编写计算过程
        pass

上述类中, WorkCenter 表示工作中心,包含工作中心的属性和方法; Technician 表示技术人员,包含技术人员的属性; Task 表示任务,包含任务的属性; Schedule 表示生产安排,包含工作中心、技术人员和任务的列表,并提供计算生产安排的方法。
在 calculate_schedule 方法中,你可以实现算法来计算生产安排。根据你提供的影响排产的因素,可以考虑使用调度算法(如贪心算法、遗传算法等)来进行任务分配和调度,以最大化机台的利用率。
具体的算法实现会根据你的具体需求和约束条件而有所不同。你可以根据上述类的基础上,结合你的业务逻辑和约束条件,编写算法来计算生产安排。在算法实现过程中,可以使用合适的数据结构和算法技巧来优化计算效率和满足约束条件。