1.前景
因为公司需要设计一个订单排产的功能,市面上的排产软件收费都不便宜,所以打算自己建设。这里打算使用OptaPlanner用作排产的规划优化引擎。但是其他文章介绍的都不够低端,导致我很多都看不懂。这里打算花一周的时间,学习一下OptaPlanner的指南,然后使用OptaPlanner进行开发。我这里选用8.44.0版本。Java版本是11,maven是3.5.4。这里建议百度一下如何安装2个java版本。
其中编写约束用的是drool,请先学习drool语法后在继续学习OptaPlanner。
这是我自己的配置(如果这里看不懂,建议就别看了)。我是小白,一下文章只是作为输出,如果有错,欢迎指出。
2.什么是OptaPlanner
每个组织都面临着规划问题:用有限的资源(员工、资产、时间和金钱)提供产品或服务。OptaPlanner优化了此类计划,以便用更少的资源做更多的业务。这被称为约束满足编程(这是运筹学学科的一部分)。这是文档的解释,我理解为凡是需要在有限的资源内,通过不同的安排来优化一些东西。那就可以用这个。比如我这次的项目是一个排产系统,他要做的是把订单分配到对应的产线上边去,需要考虑订单交期,产线产能平衡等等因素,通过订单分配到不同的产线去,来优化成本。
OptaPlanner是一个轻量级的,可嵌入的约束满足引擎,优化规划问题。通过使用先进的优化算法,OptaPlanner在合理的时间内为此类规划问题找到了接近最优的解决方案。
规划问题具有(硬和软)约束。通常,规划问题至少有两个层次的约束:
硬约束不得被打破。例如:一个老师不能同时教两个不同的课程。
如果可以避免,则不应打破(负)软约束。例如:A老师不喜欢在星期五下午上课
如果可能的话,应该满足一个积极的软约束(或奖励)。B老师喜欢在星期一上午上课。
一些基本问题(如N皇后)只有硬约束。有些问题有三个或更多层次的约束,例如硬、中、软约束
一般问题会有3种不同的约束。硬、中、软约束。硬约束是底线,中约束是道德,软约束是品质(我是这么理解的)。在后续设计中,中和软约束时通过打分不同来体现的。
OptaPlanner是一个寻找最优解的规划引擎。这个最优解的理解应该是,在给定的时间内(可配置每次解答的时间)OptaPlanner寻找到的最优的解,换句话说OptaPlanner输出的可能并不是答案的最优解,而是在有限的资源下获得的最优解。对于OptaPlanner的得到的解如何更趋近于真实的最优解,需要我们开发的时候进行优化(比如选择合适的算法(因为OptaPlanner有多种内置的算法,他会每种算法去求解,如果我们人为指定更适合这个问题的算法,那就会提升OptaPlanner的解答能力,这算高阶用法),提供更多的计算时间和资源(支持计算分布式部署))。
3.官方资料
官网:https://www.optaplanner.org/
git地址:https://github.com/kiegroup/optaplanner-quickstarts 快速入门
https://github.com/kiegroup/optaplanner/tree/8.44.x 源码
文档:https://docs.optaplanner.org/8.44.0.Final/
注意事项:
1.不同版本之间差距挺大,选择一个版本后,就不要变化了。
2.git下载的项目有2个版本,一个是源码版本,一个是快速入门版本。建议2个都下载。
3.如何只是想看项目跑起来,直接下载9.44.0即可。
点击bat即可运行例子(java11版本)
optaplanner支持和SpringBoot整合或者使用maven版本。下一篇文章将会介绍如何在本地运行起来!
4.例子运行
打开项目,首先看到的hello-world,打开TimeTableApp 并运行起来,如果你的环境没有问题的话,他就会开始对这个学校课程表问题进行排课。
问题描述:这是一个学生和教师优化学校时间表。其中对象有 教师,学生,教室,课程。我们需要安排 哪些学生在哪个教师上哪一门课程。通过OptaPlanner算出来一个答案。这个答案的解尽可能满足一下约束:
一个教室最多只能同时上一节课。
一个老师最多只能同时教一节课。
一个学生最多只能同时上一节课。
老师喜欢在同一间教室里教授所有的课程。
老师喜欢连续地教课,不喜欢课与课之间的间隙。
学生不喜欢同一科目连续上课。
正常人都可以看出来哪些是硬约束,那些是软约束,这里就不浪费笔墨。
OptaPlanner日常自夸(从数学上讲,学校排课是一个NP难问题。这意味着它很难扩展。对于一个非平凡的数据集,简单地通过所有可能的组合进行暴力迭代需要数百万年,即使在超级计算机上也是如此。幸运的是,OptaPlanner等AI约束求解器具有先进的算法,可以在合理的时间内提供接近最优的解决方案)
其中最重要的步骤之一就是 对象建模。
Timeslot类表示授课的时间间隔。例如,星期一10:30 - 11:30或星期二13:30 - 14:30。为了简单起见,所有的时间段都有相同的持续时间,午餐或其他休息时间没有时间段。
一个时间段没有日期,因为高中的时间表只是每周重复。因此,不需要持续的规划。
因为在求解过程中没有Timeslot实例更改,所以Timeslot称为**问题事实**。这些类不需要任何OptaPlanner特定的注释。
Room类表示授课的位置,例如房间A或房间B。为了简单起见,所有房间都没有容量限制,可以容纳所有上课的人。
Room实例在求解过程中不会改变,因此房间也是一个**问题事实**。
Lesson类代表 在一节课中,教师向一组学生教授一门学科,例如,9年级的数学由A.Turing教授,10年级的化学由M.Curie教授。如果同一个老师每周多次向同一个学生组教授某个科目,则存在多个只能通过id区分的Lesson实例。例如,9年级每周有六节数学课。
在求解过程中,OptaPlanner会更改Lesson类的时隙和房间字段,以将每个课程分配给一个时隙和一个房间。由于OptaPlanner更改了这些字段,因此Lesson是一个**计划实体**。
接下来的问题就是如何将Lesson分配到Room和Timeslot并且尽可能满足上边的约束。
换句话说,OptaPlanner会更改Lesson类的时隙和房间字段 这时OptaPlanner会给这种状态打个分,分数高低就决定了结果是否是在优解。其中打分的依据就是约束。OptaPlanner会更改Lesson类的时隙和房间字段这个过程就是算法。
除了橙色字段外,上一个图中的大多数字段都包含输入数据:课程的时间段和房间字段在输入数据中未分配(空),在输出数据中已分配(非空)。OptaPlanner在求解过程中更改这些字段。这些字段称为规划变量。为了让OptaPlanner识别它们,时隙和房间字段都需要一个@PlanningVariable注释。它们的包含类Lesson需要@PlanningEntity注释。
Lesson类有一个@PlanningEntity注释,所以OptaPlanner知道这个类在求解过程中会发生变化,因为它包含一个或多个规划变量。
其中Room和Timeslot有一个@PlanningVariable 这就告诉OptaPlanner这两个会有变化,那既然会变化,数据源在哪儿呢,下边会介绍
定义约束并计算分数:分数代表特定解决方案的质量。越高越好。OptaPlanner寻找最佳解决方案,即在可用时间内得分最高的解决方案。这可能是最佳解决方案。
由于此用例具有硬约束和软约束,因此使用HardSoftScore类来表示分数:
硬约束不能被打破。
软约束不应被打破。
硬约束相对于其他硬约束进行加权。软约束也相对于其他软约束进行加权。硬约束总是大于软约束,而不管它们各自的权重如何。
TimeTableEasyScoreCalculator用于计算分数。这个hello-world没有。
public class TimeTableEasyScoreCalculator implements EasyScoreCalculator<TimeTable,
HardSoftScore> {
@Override
public HardSoftScore calculateScore(TimeTable timeTable) {
List<Lesson> lessonList = timeTable.getLessonList();
int hardScore = 0;
for (Lesson a : lessonList) {
for (Lesson b : lessonList) {
if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
&& a.getId() < b.getId()) {
// A room can accommodate at most one lesson at the same time.
if (a.getRoom() != null && a.getRoom().equals(b.getRoom())) {
hardScore--;
}
// A teacher can teach at most one lesson at the same time.
if (a.getTeacher().equals(b.getTeacher())) {
hardScore--;
}
// A student can attend at most one lesson at the same time.
if (a.getStudentGroup().equals(b.getStudentGroup())) {
hardScore--;
}
}
}
}
int softScore = 0;
// Soft constraints are only implemented in the optaplanner-quickstarts code
return HardSoftScore.of(hardScore, softScore);
}
}
这种方式就是在给这个例子打分,其中满足的是硬约束,但是这种方式并不推荐,因为他是非增量的:每次一节课被分配到不同的时间段或房间时,所有的课程都会被重新评估以计算新的分数。
接下来看看hello-world中的TimeTableConstraintProvider 他是现在OptaPlanner比较推荐的一直约束方式。(看不懂没关系,后续会介绍)
还有TimeTable类没有介绍,它负责收集规划解决方案中的域对象。
TimeTable包装单个数据集的所有Timeslot、Room和Lesson实例。此外,因为它包含所有课程,每个课程都有一个特定的计划变量状态,所以它是一个计划解决方案,并且它有一个分数。
HardSoftScore状态:
如果课程仍然未分配,则它是未初始化的解决方案,例如,得分为-4init/0hard/0 soft的解决方案。
如果它打破了硬约束,那么它是一个不可行的解决方案,例如,得分为-2hard/-3soft的解决方案。
如果它符合所有硬约束,则它是可行的解决方案,例如,得分为0 hard/-7soft的解决方案。
TimeTable类有一个@PlanningSolution注释,所以OptaPlanner知道这个类包含所有的输入和输出数据。他是问题的输入。
timeslotList包含所有的时隙。这是一个问题事实的列表,因为它们在解决过程中不会改变
roomList包含所有房间。这是一个问题事实的列表,因为它们在解决过程中不会改变
lessonList包含所有课程。这是规划图元的列表,因为它们在求解过程中会发生更改。
这里的问题事实和计划实体中,问题事实不会变化其实是比较难以理解的。问题事实是不可变的、与规划问题相关的信息。这可能是问题的初始状态,其中包含问题的静态数据,而不是变化的计划。通常,
问题事实是在规划问题开始时被加载的信息,而在解决问题的过程中不会发生变化。
计划实体是问题中可变的、可以通过规划决策进行优化的对象。这些实体代表问题中的可调度单位,例如任务、资源、安排等,其状态可以在规划过程中改变以找到最佳解决方案。
timeslotList字段是一个值范围提供程序。它保存了OptaPlanner可以从中挑选的时隙实例,以分配给Lesson实例的时隙字段。timeslotList字段有一个@ValueRangeProvider注释,通过将计划变量的类型与值范围提供程序返回的类型进行匹配,将@PlanningVariable(valueRangeProviderRefs = "xx")与@ValueRangeProvider(id = “xx")连接起来。如果不填写id和valueRangeProviderRefs ,OptaPlanner会尝试自己寻找。
以上就是这个例子的简单介绍,其中还可以运行SpringBoot版本的。如下图,TimeTableSpringBootApp启动以后,访问127.0.0.1:8080即可,原理相同,后续会详细介绍与hello-world的不同。