diff --git "a/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266.md" "b/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266.md" new file mode 100644 index 0000000..2cfc41b --- /dev/null +++ "b/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266.md" @@ -0,0 +1,30 @@ +--- +categories: [optimization, mathematics] +tags: [job shop schedule] +--- + +# 作业车间调度问题求解框架 + +--- + +组合优化问题广泛出现于各行各业,例如生产制造排程,机场航班调度、项目管理计划等,求解这类问题有助于解决实际应用中的调度难题、提高生产效率;考虑到实际的业务逻辑因行业而异且可能相当复杂,本系列文章针对调度类问题的基本模型——作业车间调度(Job Shop Schedule),基于Python开发了一个通用的求解框架: + +- 参考文献实现了一些基本解法例如基于规则指派、局部搜索、群体方法等 + +- 便于快速实施和测试新算法(参考 [Python建模](2021-08-14-作业车间调度问题求解框架:Python建模.md)) + + +## 项目仓库 + +https://github.com/dothinking/job_shop_schedule + +## 建模 + +- [问题描述](2021-08-08-作业车间调度问题求解框架:问题描述.md) + +- [Python建模](2021-08-14-作业车间调度问题求解框架:Python建模.md) + +- [基于matplotlib的动态甘特图](2021-08-15-基于matplotlib的动态甘特图.md) + + +## 求解 diff --git "a/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" "b/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" similarity index 99% rename from "docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" rename to "docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" index 098ddca..411a18b 100644 --- "a/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" +++ "b/docs/2021-08-08-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232\351\227\256\351\242\230\346\217\217\350\277\260.md" @@ -3,7 +3,7 @@ categories: [optimization, mathematics] tags: [job shop schedule] --- -# 作业车间调度问题:问题描述 +# 作业车间调度问题求解框架:问题描述 --- diff --git "a/docs/2021-08-14-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232Python\345\273\272\346\250\241.md" "b/docs/2021-08-14-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232Python\345\273\272\346\250\241.md" new file mode 100644 index 0000000..adac5b2 --- /dev/null +++ "b/docs/2021-08-14-\344\275\234\344\270\232\350\275\246\351\227\264\350\260\203\345\272\246\351\227\256\351\242\230\346\261\202\350\247\243\346\241\206\346\236\266\357\274\232Python\345\273\272\346\250\241.md" @@ -0,0 +1,134 @@ +--- +categories: [optimization, python/vba/cpp] +tags: [job shop schedule] +--- + +# 作业车间调度问题求解框架:Python建模 + +--- + +基于前文对作业车间调度问题的定义和描述,本文进行Python建模,开发了一个通用的作业车间调度问题求解框架,把问题分解、抽象为可重用的部分,从而专注求解算法的开发、实施和验证。 + +> [dothinking/job_shop_schedule](https://github.com/dothinking/job_shop_schedule) + + +## 框架结构 + +整个求解框架基于Python面向对象编程实现,主要结构参考下图。 + +![class-diagram](images/2021-08-14-01.png) + + +其中,所有对象按用途可以归为三类: + + +### (a) 物理对象 + +- `Job` 作业实体 +- `Machine` 机器实体 +- `Operation` 工序实体,包含所属作业、分配的机器、加工时长等属性 + + +### (b) 求解变量 + +`OperationStep`是工序实体`Operation` 的封装,同时加上待求解的参数 `start_time`。根据前文关于作业车间问题的两种不同的描述方式,相应有两种不同的求解思路: + +- 对于以`start_time`为变量描述的数学模型,直接求解`start_time`即可 +- 对于以 **析取图** 描述的模型,需要先求解工序的顺序,然后递推出`start_time` + +因此,对于析取图描述的模型,还提供了以下中间属性: + +- 继承自 `JobStep` 的 `pre_job_op` 和 `next_job_op`,分别表示当前工序在所属作业实体上的顺序:前一道工序和下一道工序;并且,它们是已知的。 + +- 继承自 `MachineStep` 的 `pre_machine_op` 和 `next_machine_op`,分别表示当前工序在分配机器上的加工顺序:前一道工序和下一道工序;注意这个顺序即为需要求解的变量。 + + +### (c) 求解流程 + +`JSProblem` 是所有工序实体 `Operation` 的封装: + +- 它的解为一个`JSSolution`实例 +- 每当获得一个更好的解,**需要使用`update_solution()`方法显式更新** + +`JSSolution` 是所有变量 `OperationStep` 的封装: + +- 对于析取图求解模型,需要显式地调用 `evaluate()` 来基于求解的顺序计算最终变量 `start_time`;基于数学模型则无需这一步。 + +- `is_feasible()` 判断一个解是否满足所有约束 + +- 如果是一个可行解,`makespan`属性得到最大加工周期长度 + + +`JSSolver` 是作业车间调度问题求解器的基类,便于继承此基类后实施新算法。 + + +## 实施新算法 + +以上的设计可以避免重复工作,从而专注于算法本身的实现和测试。基于此框架,实施新算法的只需创建自定义求解器类,然后继承 `JSSolver` 并实现 `do_solver()` 方法。`do_solver()` 方法内部主要分为三大步骤: + +- 基于问题创建初始状态的解(注意并非可行的 **初始解**) + + ```python + solution = JSSolution(problem) + ``` + +- 实施算法,计算或者优化这个解 + + - 对于以`start_time`为变量描述的数学模型,直接求解每个工序的`start_time`即可 + - **对于以析取图描述的模型,需要先求解工序的顺序,然后显式地调用 `solution.evaluate()` 递推出`start_time`** + + +- 每次迭代得到更好的解后,显式更新问题的解 + + ```python + problem.update_solution(solution) + ``` + +关键代码参考: + +```python +from model.solver import JSSolver +from model.problem import JSProblem +from model.solution import JSSolution + +class UserSolver(JSSolver): + + def do_solve(self, problem:JSProblem): + '''User defined solving process.''' + + # (1) Initialize an empty solution from problem + solution = JSSolution(problem) + + # (2) Solve or optimize the solution, + # i.e. determine the start_time of OperationStep instances. + # Note to evaluate solution explicitly if disjunctive graph model. + ... + # solution.evaluate() + + # (3) Update the solution for problem iteratively + problem.update_solution(solution) +``` + + +## 测试算法 + +框架已经内置了作业车间调度问题的标准考题数据,便于根据名称直接初始化问题。以下示例调用自定义求解器 `SampleSolver` 求解 `ft10` 问题。 + +- 更多基本问题数据 [参考](https://github.com/dothinking/job_shop_schedule/blob/master/benchmark/instances.json) + +- 求解过程以甘特图形式动态展示解的变化 + + +```python +from model.problem import JSProblem +from solver.SampleSolver import SampleSolver + +# load benchmark problem +problem = JSProblem(benchmark='ft10') + +# solve problem with user defined solver +s = SampleSolver(name='sample') +s.solve(problem=problem) +``` + +基本介绍到此结束,接下来将陆续实施一些求解算法。 \ No newline at end of file diff --git a/docs/images/2021-08-14-01.png b/docs/images/2021-08-14-01.png new file mode 100644 index 0000000..f8b0ba6 Binary files /dev/null and b/docs/images/2021-08-14-01.png differ