2.2.8. 创建应用程序
- 创建SolverFactory 来为每个数据集构建Solver
- 加载数据集
- 使用Solver.solve()进行求解
- 输出数据集的解决方案
通常一个应用包含一个SolverFactory 来为每个要求解的问题数据集构建新的Solver实例。SolverFactory是线程安全的,但Solver不是。
代码语言:javascript复制import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.SolverConfig;
public class TimeTableApp {
...
public static void main(String[] args) {
// 创建求解器工厂实例
SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
// 注册规划方案类 @PlanningSolution
.withSolutionClass(TimeTable.class)
// 注册规划实体类 @PlanningEntity
.withEntityClasses(Lesson.class)
// 注册约束提供者类 ConstraintProvider
.withConstraintProviderClass(TimeTableConstraintProvider.class)
// 配置求解器执行时间限制,建议至少5分钟
.withTerminationSpentLimit(Duration.ofSeconds(5)));
// 加载问题数据集
TimeTable problem = generateDemoData();
// 求解问题
Solver<TimeTable> solver = solverFactory.buildSolver();
TimeTable solution = solver.solve(problem);
// 输出解决方案
printTimetable(solution);
}
...
}
注意:如果没有终止设置或者terminationEarly()事件,求解器将一直运行。
OptaPlanner返回在可用终止时间内找到的最优方案。 由于NP困难问题的性质(9.2),最优方案可能不是最佳的,尤其是对于较大的数据集。 增加终止时间以可能找到更好的方案。
2.2.9.2. 测试应用程序
2.2.9.2.1. 测试约束
可使用ConstraintVerifier对每一种约束条件进行单元测试
代码语言:javascript复制import org.junit.jupiter.api.Test;
import org.optaplanner.test.api.score.stream.ConstraintVerifier;
class TimeTableConstraintProviderTest {
private static final Room ROOM1 = new Room("Room1");
private static final Timeslot TIMESLOT1 = new Timeslot(DayOfWeek.MONDAY, LocalTime.NOON);
private static final Timeslot TIMESLOT2 = new Timeslot(DayOfWeek.TUESDAY, LocalTime.NOON);
// 构建约束校验器,入参:约束供应者实例,规划方案类,规划实体类
ConstraintVerifier<TimeTableConstraintProvider, TimeTable> constraintVerifier = ConstraintVerifier.build(
new TimeTableConstraintProvider(), TimeTable.class, Lesson.class);
@Test
void roomConflict() {
// 构建规划实体数据集合,验证约束是否正确的进行惩罚
Lesson firstLesson = new Lesson(1, "Subject1", "Teacher1", "Group1", TIMESLOT1, ROOM1);
Lesson conflictingLesson = new Lesson(2, "Subject2", "Teacher2", "Group2", TIMESLOT1, ROOM1);
Lesson nonConflictingLesson = new Lesson(3, "Subject3", "Teacher3", "Group3", TIMESLOT2, ROOM1);
constraintVerifier.verifyThat(TimeTableConstraintProvider::roomConflict)
.given(firstLesson, conflictingLesson, nonConflictingLesson)
.penalizesBy(1);
}
}
注意:因为约束权重在投入生产运行前经常更改。ConstraintVerifier在测试中忽略约束权重,即使这些约束权重是在ConstraintProvider中硬编码的。这样,约束权重的调整就不会破坏单元测试。
如果测试失败将报错如:
java.lang.AssertionError: Broken expectation. Constraint: example.domain/Room conflict Expected penalty: 2 (class java.lang.Integer) Actual penalty: 1 (class java.lang.Integer) Explanation of score (-1hard/0soft): Constraint match totals: -1hard: constraint (Room conflict) has 1 matches: -1hard: justifications ([Subject1(1), Subject2(2)]) Indictments: -1hard: indicted object (Subject1(1)) has 1 matches: -1hard: constraint (Room conflict) -1hard: indicted object (Subject2(2)) has 1 matches: -1hard: constraint (Room conflict)
参考Testing Constraint Streams