本文基于 OptaPlanner 官方文档的 2.2. Hello world Java quick start,根据实际情况做出了修改。新手勿喷。
是什么?
OptaPlanner 是一个轻量级、可嵌入的规划引擎 / AI 约束求解器。
OptaPlanner 使用 java 语言开发,它在内部封装了各种 AI 优化算法(如禁忌搜索,模拟退火等),可让开发者透过其 API 即可求解 NPC / NP-Hard 问题。
什么是P问题、NP问题和NPC问题,下面是本人总结出的:
NP 问题:
- 无法用确定的算法直接求解(确定的算法如:y=2x+1),但对于在一定时间内得出的解(如通过遍历或猜测取得),是可进行验证的。
NPC 问题:
- 不但无法用确定的算法直接求解,对所得的解,也没有一个确定的办法去验证的问题。1. NPC 问题也是一个 NP 问题,2. 所有的NP问题都可以约化到它(约化具备传递性,NPC 约化为-> NP 约化为-> 它)。
- 官方描述:在合理的时间内验证给定的问题解决方案很容易。
NP-Hard 问题:
- 不一定是 NP 问题,但所有的 NP 问题都可以约化到它。
- 官方描述:没有灵丹妙药可以在合理的时间内找到问题的最佳解决方案(目前)。
要干什么?
通过本教程,你将能创建一张符合要求(约束)的课程表。类似这样:
房间A 房间B 房间C
|------------|------------|------------|------------|
MON 08:30 语文 化学 英语
张 老师 里 老师 钟 老师
1 班 2 班 4 班
|------------|------------|------------|------------|
MON 09:30 数学 生物 物理
李 老师 达 老师 牛 老师
1 班 2 班 4 班
|------------|------------|------------|------------|
MON 10:30 英语 语文 化学
钟 老师 张 老师 里 老师
1 班 3 班 4 班
|------------|------------|------------|------------|
MON 13:30 物理 数学 生物
牛 老师 李 老师 达 老师
1 班 3 班 4 班
|------------|------------|------------|------------|
MON 14:30 化学 英语 语文
里 老师 钟 老师 张 老师
1 班 3 班 5 班
|------------|------------|------------|------------|
TUE 08:30 生物 物理 数学
达 老师 牛 老师 李 老师
1 班 3 班 5 班
|------------|------------|------------|------------|
TUE 09:30 语文 化学 英语
张 老师 里 老师 钟 老师
2 班 3 班 5 班
|------------|------------|------------|------------|
TUE 10:30 数学 生物 物理
李 老师 达 老师 牛 老师
2 班 3 班 5 班
|------------|------------|------------|------------|
TUE 13:30 英语 语文 化学
钟 老师 张 老师 里 老师
2 班 4 班 5 班
|------------|------------|------------|------------|
TUE 14:30 物理 数学 生物
牛 老师 李 老师 达 老师
2 班 4 班 5 班
|------------|------------|------------|------------|
进程已结束,退出代码0
课程表规划问题是 NP-Hard 问题,即没有灵丹妙药可以在合理的时间内找到问题的最佳解决方案。
OptaPlanner 等 AI 约束求解器拥有先进的算法,可以在合理的时间内提供接近最优的解决方案。
我们将会学习到 OptaPlanner 的几个基本概念:
约束、问题事实、规划实体、规划变量、解决方案。
确定需求(约束)
根据一般情况,我们有以下需求:
- 一个房间最多可以同时上一节课
- 一位老师最多可以同时教一堂课
- 一个班级最多可以同时上一堂课
- 老师更喜欢在同一个房间里教授所有课程
- 老师喜欢连续上课,不喜欢课程之间的空隙(连堂)
- 学生不喜欢连续上同样课程的课
在 OptaPlanner 中,上面的需求被称为 约束,可分为 硬约束 和 软约束。
- 硬约束:OptaPlanner 在规划的过程中必须遵守,如【1-3】条
- 软约束:可不必遵守,遵守了更好,如【4-6】条
建立模型
我们需要根据需求创建三个实体模型。
我们需要为每节课分配到合适的房间和时间段。
在 OPtaPlanner 上有 问题事实、规划实体、规划变量 概念。
& 对于课程时间段
-
如:
- MON 08:30 - 09:30,星期一 八点半 到 九点半
- TUE 10:30 - 11:30,星期二 十点半 到 十一点半
-
在 OPtaPlanner 求解前,课程时间段需要用户输入;在 OPtaPlanner 求解时,各个时间段实例没有发生变化,此即为 问题事实
& 对于房间:
-
如:
- 房间A
- 房间B
-
在 OPtaPlanner 求解前,房间需要用户输入;在 OPtaPlanner 求解时,各个房间实例没有发生变化,此即为 问题事实
& 对于课程
-
如:
- {id=0, subject='语文', teacher='张 老师', studentGroup='1 班', timeslot=MONDAY 08:30, room=房间A}
- {id=1, subject='数学', teacher='李 老师', studentGroup='1 班', timeslot=MONDAY 09:30, room=房间A}
-
在 OPtaPlanner 求解时,会更改课程的 时间段 字段和 房间 字段,这样一节课就确定规划下来了,这些更改的字段叫 规划变量(图中橙色部分)。因为 OptaPlanner 更改了课程的字段值,所以课程是一个 规划实体(图中绿色部分)。
定义约束(代码)并计算得分
OptaPlanner 在求解规则问题的过程中会产生很多的解(即解决方案)。
- 可能解:一个规划问题的任意一个解都称为可能解
- 可行解:可行解就是那些完全符合硬约束的解
- 相对最优解:在约定的时间内(如2分钟内)OptaPlanner 能找到的最好的解。
OptaPlanner 是如何找到的相对最优解的?需要我们根据需求编写约束代码,对找到的解进行打分。
接下来我们将阅读部分示例代码,无需担心,已帮忙探路(其实就是注释翻译得尽量详细点),下一篇会详细讲如何搭建项目,大概看一下就行。
首先是 TimeTableConstraintProvider
类,它负责告诉 OptaPlanner 有哪些 硬约束 和 软约束。
//课程表约束提供者
public class TimeTableConstraintProvider implements ConstraintProvider {
// 定义约束条件
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
// 硬约束
// 一个房间最多可以同时上一节课
roomConflict(constraintFactory),
// 一位老师最多可以同时教一堂课
teacherConflict(constraintFactory),
// 一个班级最多可以同时上一堂课
studentGroupConflict(constraintFactory),
// -----
// 软约束
// 老师更喜欢在同一个房间里教授所有课程
teacherRoomStability(constraintFactory),
// 老师喜欢连续上课,不喜欢课程之间的空隙(连堂)
teacherTimeEfficiency(constraintFactory),
// 学生不喜欢连续上同样课程的课
studentGroupSubjectVariety(constraintFactory)
};
}
// ...
}
我们阅读其中的第一个硬约束 roomConflict(constraintFact)
。
// 关于房间的硬约束
Constraint roomConflict(ConstraintFactory constraintFactory) {
// 一个房间在同一时间最多只能容纳一个课程。
return constraintFactory
// 选择2个不同的课程配对
.forEachUniquePair(Lesson.class,
// 1. 在同一时间段
Joiners.equal(Lesson::getTimeslot),
// 2. 在同一房间
Joiners.equal(Lesson::getRoom))
// 对每一对这种情况进行沉重的惩罚
.penalize("Room conflict", HardSoftScore.ONE_HARD);
}
如果存在 两个 1. 在同一时间段 和 2. 在同一房间 的不同课程,那么将进行沉重的(HardSoftScore.ONE_HARD
)惩罚(penalize
)。这表明是一个硬约束(ONE_HARD),必须不能出现。
除了惩罚, OptaPlanner 还有 奖励(reward)。
// ...
.reward("Teacher time efficiency", HardSoftScore.ONE_SOFT);
这句代码表示,这是一个软约束(ONE_SOFT),当出现时会给予奖励(reward)。
// ...
.penalize("Teacher time efficiency", HardSoftScore.ONE_SOFT);
这句代码表示,这是一个软约束(ONE_SOFT),当出现时会给予“适当”的惩罚(penalize)。
不知不觉写了快 2000 字了,本篇主要是讲 OptaPlanner 的一些概念,一来巩固本人的学习成果,二来想为 OptaPlanner 社区尽微薄之力。希望能帮助到和我一样刚入门的新人朋友们。
下一篇将会从零到一,搭建课程表规划的示例项目。