3.6. 规划问题和规划解决方案
3.6.1. 规划问题实例
规划问题的数据集需要包装在一个类中以Solver供解决。该解决方案类同时代表了规划问题和(求解结束后)解决方案。它被@PlanningSolution注释。例如在 n 个皇后中,解类是NQueens类,它包含一个Column列表、一个Row列表和一个Queen列表。
一个规划问题实际上是一个未解决的规划解决方案,或者 - 换句话说 - 一个未初始化的解决方案。
例如,在云资源优化例子中,那个CloudBalance类有@PlanningSolution注解,然而未解决的processList类中的每个CloudProcess都还没有被分配到一个计算机(他们的computer属性为空)。这不是一个可行的解决方案,它甚至不是一个可能的解决方案,这是一个未初始化的解决方案。
3.6.2. 解决方案类
一个Solution类包含所有ProblemFact问题事实、PlanningEntity计划实体和Score分数。它带有@PlanningSolution注释。例如,一个NQueens实例包含所有列、所有行和所有Queen实例的列表:
@PlanningSolution
public class NQueens {
// Problem facts
private int n;
private List<Column> columnList;
private List<Row> rowList;
// Planning entities
private List<Queen> queenList;
private SimpleScore score;
...
}
求解器配置需要声明规划求解类:
<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
...
<solutionClass>org.optaplanner.examples.nqueens.domain.NQueens</solutionClass>
...
</solver>
3.6.3. Solution解决方案的PlanningEntity规划实体
OptaPlanner 需要从jSolution解决方案实例中提取实体实例PlanningEntity。它通过调用每个带有注释的getter(或字段)来获取这些集合
@PlanningSolution
public class NQueens {
...
private List<Queen> queenList;
@PlanningEntityCollectionProperty
public List<Queen> getQueenList() {
return queenList;
}
}
可以有多个@PlanningEntityCollectionProperty注解的属性。那些甚至可以返回一个具有相同实体类类型的集合。它也可以返回一个数组。
@PlanningEntityCollectionProperty注解需要在具有@PlanningSolution注解的类中的一个属性上。它在没有该注解的父类或子类中被忽略。
在极少数情况下,一个规划实体可能是一个属性:在其getter方法(或字段)上使用@PlanningEntityProperty来代替。如果启用,这两个注解也可以被自动发现。
3.6.4. Score解决方案
一个@PlanningSolution类需要一个 score 属性(或字段),该属性使用注释进行@PlanningScore注释。如果分数还没有被计算,那么分数属性就是空的。分数属性的类型与你的用例的具体分数实现有关。例如,NQueens使用SimpleScore:
@PlanningSolution
public class NQueens {
...
private SimpleScore score;
@PlanningScore
public SimpleScore getScore() {
return score;
}
public void setScore(SimpleScore score) {
this.score = score;
}
}
大多数用例使用HardSoftScore:
@PlanningSolution
public class CloudBalance {
...
private HardSoftScore score;
@PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
}
有些用例会使用其他分数类型。
3.6.5。解决方案的问题事实 ProblemFact
对于Constraint Streams和Drools 分数计算,OptaPlanner 需要从Solution解决方案实例中提取ProblemFact问题事实实例。它通过调用每个带有@ProblemFactCollectionProperty注释的方法(或字段)来获取这些集合@。这些方法返回的所有对象都可供 Constraint Streams 或 Drools 规则使用。例如,在所有NQueens情况下都是问题事实。ColumnRow
@PlanningSolution
public class NQueens {
...
private List<Column> columnList;
private List<Row> rowList;
@ProblemFactCollectionProperty
public List<Column> getColumnList() {
return columnList;
}
@ProblemFactCollectionProperty
public List<Row> getRowList() {
return rowList;
}
}
所有ProblemFact规划实体都会自动插入Drools工作内存中,请注意在它们的属性上添加注解。
可以有多个@ProblemFactCollectionProperty带注释的属性。这些属性甚至可以返回具有相同类类型的集合,但它们不应该两次返回相同的实例。它也可以返回一个数组。
@ProblemFactCollectionProperty 注释需要位于具有@PlanningSolution 注释的类的成员上。没有该注释的父类或子类将忽略它。
在极少数情况下,问题事实可能是对象:@ProblemFactProperty改为在其方法(或字段)上使用。
扩展ProblemFact
有些ProblemFact一开始并未在业务模型中的体现,但是这些ProblemFact数据可以简化约束规则的编写,在提交求解之前,将这些数据计算出来作为一个ProblemFact放在Solution类内,可以是求解器更快、更简单的进行求解。
例如,在检查中,每两个至少共享一个学生的考试科目,就会创建一个的ProblemFact问题事实TopicConflict。
@ProblemFactCollectionProperty
private List<TopicConflict> calculateTopicConflictList() {
List<TopicConflict> topicConflictList = new ArrayList<TopicConflict>();
for (Topic leftTopic : topicList) {
for (Topic rightTopic : topicList) {
if (leftTopic.getId() < rightTopic.getId()) {
int studentSize = 0;
for (Student student : leftTopic.getStudentList()) {
if (rightTopic.getStudentList().contains(student)) {
studentSize++;
}
}
if (studentSize > 0) {
topicConflictList.add(new TopicConflict(leftTopic, rightTopic, studentSize));
}
}
}
}
return topicConflictList;
}
如果分数约束需要检查:是否有两个具有共同学生的主题的考试被安排在一起(取决于约束:同时、连续或同一天),该TopicConflict实例可以用作问题事实,而不是必须组合每两个Student实例。在求解过程中去计算,这样会大大降低OptaPlanner求解的效率。
3.6.6。自动发现解决方案属性
与其明确配置每个属性(或字段)注解,有些也可以由OptaPlanner自动推导出来。例如,在CloudBalance示例上:
```java
@PlanningSolution(autoDiscoverMemberType = AutoDiscoverMemberType.FIELD)
public class CloudBalance {
// Auto discovered as @ProblemFactCollectionProperty
@ValueRangeProvider(id = "computerRange") // Not (yet) auto discovered
private List<CloudComputer> computerList;
// Auto discovered as @PlanningEntityCollectionProperty
private List<CloudProcess> processList;
// Auto discovered as @PlanningScore
private HardSoftScore score;
...
}
AutoDiscoverMemberType可以是:
NONE: 没有自动发现。
FIELD@PlanningSolution:自动发现类上的所有字段
GETTER@PlanningSolution: 自动发现类中的所有 getter
自动注解基于字段类型(或 getter 返回类型):
@ProblemFactProperty: 当它不是 a Collection、数组、@PlanningEntity类或 a时Score
@ProblemFactCollectionProperty:当它Collection是非@PlanningEntity类的类型(或数组)时
@PlanningEntityProperty: 当它是一个配置的@PlanningEntity类或子类时
@PlanningEntityCollectionProperty: 当它是Collection一个类型的(或数组)是配置的@PlanningEntity类或子类时
@PlanningScore: 当它是一个Score或子类时
这些自动注释仍然可以按字段(或 getter)覆盖。具体来说,BendableScore始终需要使用显式注释来覆盖@PlanningScore以定义硬级别和软级别的数量。
3.6.8。创建未初始化的解决方案
创建一个@PlanningSolution实例来表示您的规划问题的数据集,因此可以将其设置Solver为要解决的规划问题。例如,在 n 个皇后中,NQueens创建一个实例,其中包含所需的Column和Row实例,并且每个Queen集合都为不同的集合column,每个row集合为null。
private NQueens createNQueens(int n) {
NQueens nQueens = new NQueens();
nQueens.setId(0L);
nQueens.setN(n);
nQueens.setColumnList(createColumnList(nQueens));
nQueens.setRowList(createRowList(nQueens));
nQueens.setQueenList(createQueenList(nQueens));
return nQueens;
}
private List<Queen> createQueenList(NQueens nQueens) {
int n = nQueens.getN();
List<Queen> queenList = new ArrayList<Queen>(n);
long id = 0L;
for (Column column : nQueens.getColumnList()) {
Queen queen = new Queen();
queen.setId(id);
id++;
queen.setColumn(column);
// Notice that we leave the PlanningVariable properties on null
queenList.add(queen);
}
return queenList;
}
图 1. 四皇后拼图的未初始化解决方案
通常,大部分数据来自您的数据层,您的解决方案实施只是聚合该数据并创建未初始化的计划实体实例来计划:
private void createLectureList(CourseSchedule schedule) {
List<Course> courseList = schedule.getCourseList();
List<Lecture> lectureList = new ArrayList<Lecture>(courseList.size());
long id = 0L;
for (Course course : courseList) {
for (int i = 0; i < course.getLectureSize(); i++) {
Lecture lecture = new Lecture();
lecture.setId(id);
id++;
lecture.setCourse(course);
lecture.setLectureIndexInCourse(i);
// Notice that we leave the PlanningVariable properties (period and room) on null
lectureList.add(lecture);
}
}
schedule.setLectureList(lectureList);
}