2.2.8. 创建应用程序
- 创建SolverFactory 来为每个数据集构建Solver
- 加载数据集
- 使用Solver.solve()进行求解
- 输出数据集的解决方案
通常一个应用包含一个SolverFactory 来为每个要求解的问题数据集构建新的Solver实例。SolverFactory是线程安全的,但Solver不是。
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对每一种约束条件进行单元测试
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);@Testvoid 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