22 业务模型建立

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

内容概要

今天我们来学习一个如何建立一个业务模型,有哪些概念是需要我们掌握的,例如:ProblemFact、PlanningEntity、ProblemSolution,以及这些对象中的字段有什么作用。

如何区分ProblemFact和PlanningEntity

我们来看一下规划问题的数据,会认识到其中的对象,每一个都可以被归类为以下的一种:

  • 无任何关联的类:不被任何分数约束所使用,从规划的角度来看,这个数据是垃圾数据。
  • Problem Fact事实类:由分数约束使用,但在求解期间不改变(只要问题保持不变)。比如说。床位、房间、班次、人员、时段….问题事实类的所有属性都是问题属性。
  • Planning Entity规划实体类:由分数约束使用,在求解期间改变。比如说。CloudProcess, TimeTable, Exam, ……在求解过程中改变的属性是Planning Variable规划变量,其他属性是问题属性。
    问问自己,在求解期间,什么类会发生变化?哪个类有我想让求解器为我改变的变量?这个类就是一个Planning Entity规划实体。大多数用例只有一个规划实体类,大多数用例的每个规划实体类也只有一个规划变量。

在OptaPlanner中,所有Problem FactPlanning Entity都是普通的JavaBeans(POJOs)。

Problem Fact

Problem Fact问题事实类是任何带有getter方法的JavaBean(POJO),在求解过程中不会改变。例如,云资源优化例子中,Computer是Problem Fact问题事实。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
java复制代码@XStreamAlias("CloudComputer")
public class CloudComputer extends AbstractPersistable implements Labeled {

private int cpuPower; // in gigahertz
private int memory; // in gigabyte RAM
private int networkBandwidth; // in gigabyte per hour
private int cost; // in euro per month

public CloudComputer() {
}

public CloudComputer(long id, int cpuPower, int memory, int networkBandwidth, int cost) {
super(id);
this.cpuPower = cpuPower;
this.memory = memory;
this.networkBandwidth = networkBandwidth;
this.cost = cost;
}

public int getCpuPower() {
return cpuPower;
}

public void setCpuPower(int cpuPower) {
this.cpuPower = cpuPower;
}

public int getMemory() {
return memory;
}

public void setMemory(int memory) {
this.memory = memory;
}

public int getNetworkBandwidth() {
return networkBandwidth;
}

public void setNetworkBandwidth(int networkBandwidth) {
this.networkBandwidth = networkBandwidth;
}

public int getCost() {
return cost;
}

public void setCost(int cost) {
this.cost = cost;
}

// ************************************************************************
// Complex methods
// ************************************************************************

public int getMultiplicand() {
return cpuPower * memory * networkBandwidth;
}

@Override
public String getLabel() {
return "Computer " + id;
}

}

当然,一个问题事实可以参包含他问题事实。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class Course {

private String code;

private Teacher teacher; // Other problem fact
private int lectureSize;
private int minWorkingDaySize;

private List<Curriculum> curriculumList; // Other problem facts
private int studentSize;

// ... getters
}

一个问题事实类不需要任何OptaPlanner的注解。

Planning entity

Planning entity annotation

Planning Entity规划实体是一个JavaBean(POJO),在求解过程中会发生变化,例如一个CloudProcess会换成另一台Computer。一个规划问题有多个规划实体,例如对于云资源优化问题,每个CloudProcess都是一个规划实体。但通常只有一个规划实体类,例如CloudProcess类

一个规划实体类,需要增加@PlanningEntity注解。

每个规划实体类有一个或多个Planning Variable规划变量。它还应该有一个或多个定义性的属性。例如,CloudProcess类中,computer作为一个规划变量,在求解过程中会发生改变,而requiredCpuPowerrequiredMemoryrequiredNetworkBandwidth作为一个线程的其它信息,求解过程中不会发生改变。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class)
@XStreamAlias("CloudProcess")
public class CloudProcess extends AbstractPersistable {

private int requiredCpuPower; // in gigahertz
private int requiredMemory; // in gigabyte RAM
private int requiredNetworkBandwidth; // in gigabyte per hour

// Planning variables: 规划变量:规划期间的变化,分数计算之间的变化。
private CloudComputer computer;
......
}

一个规划实体类可以有多个规划变量。例如课程时间表例子,一节课程,是有老师在某个时间段上在某个教室进行授课,所以Lesson它有两个规划变量,timeslot时段和room教室。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@PlanningEntity
public class Lesson {

private Long id;

private String subject;
private String teacher;
private String studentGroup;

@PlanningVariable(valueRangeProviderRefs = "timeslotRange")
private Timeslot timeslot;

@PlanningVariable(valueRangeProviderRefs = "roomRange")
private Room room;
....
}

求解器配置需要声明每个规划实体类。

1
2
3
4
5
6
xml复制代码<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">
...
<entityClass>org.optaplanner.examples.cloudbalancing.domain.CloudProcess</entityClass>
...
</solver>

Planning Entity比较器

一些优化算法如果能估算出哪些规划实体更难规划,就能更有效地工作。例如:在垃圾箱包装中,更大的物品更难装入,在课程安排中,有更多学生的讲座更难安排,而在n个皇后中,中间的皇后更放在棋盘上。

不要尝试使用Comparator比较器来实现业务约束。它不会影响评分函数:如果我们有无限的求解时间,返回的解决方案将是一样的。

为了达到某些实体在日程表中被安排得更早的目的,添加一个分数约束来改变分数函数,使其更倾向于这种解决方案。只有在可以使求解器更有效率的情况下,才考虑也增加规划实体的Comparator。

为了让启发式方法能够利用当前优化问题的特殊信息,需要@PlanningEntity注解设置 difficultyComparatorClass属性。

1
2
3
4
java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class)
public class CloudProcess {
// ...
}
1
2
3
4
5
6
7
8
9
10
java复制代码public class CloudProcessDifficultyComparator implements Comparator<CloudProcess> {

public int compare(CloudProcess a, CloudProcess b) {
return new CompareToBuilder()
.append(a.getRequiredMultiplicand(), b.getRequiredMultiplicand())
.append(a.getId(), b.getId())
.toComparison();
}

}

比较器应该从高到低实现:容易的实体比较低,困难的实体比较高。例如,在垃圾箱包装中:小物品<中物品<大物品。
虽然大多数算法都是先从较难的实体开始,但它们只是把顺序颠倒过来。

针对云平衡CloudBalance的例子,它的比较器就是将所需资源(cpuPowermemorynetWorkBandwidth)更多的进程优先排在前面,让求解器可以优先解决这些需求比较大的进程。

当前的规划变量都不应该被用来比较规划实体的难度。在构建启发式方法期间,这些变量无论如何都可能是空的。例如,不应该使用CloudProcess中的computer

Planning variable规划变量

Planning variable注解

Planning Variable规划变量是Planning Entity上的一个JavaBean属性(所以是一个gettersetter)。它指向一个规划值,该值在规划过程中会发生变化。例如,CloudProcesscomputer属性就是一个真正的规划变量。高级情况下也可以有阴影变量,后续我们会学习到。

Planning Variable规划变量getter方法需要添加@PlanningVariable注解,并且需要一个非空的valueRangeProviderRefs属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class)
@XStreamAlias("CloudProcess")
public class CloudProcess extends AbstractPersistable {

private int requiredCpuPower; // in gigahertz
private int requiredMemory; // in gigabyte RAM
private int requiredNetworkBandwidth; // in gigabyte per hour

// Planning variables: changes during planning, between score calculations.
private CloudComputer computer;

......

@PlanningVariable(valueRangeProviderRefs = {
"computerRange" }, strengthComparatorClass = CloudComputerStrengthComparator.class)
public CloudComputer getComputer() {
return computer;
}

public void setComputer(CloudComputer computer) {
this.computer = computer;
}
......

}

valueRangeProviderRefs属性定义了此规划变量的可能规划值。它引用了一个或多个@ValueRangeProvider id

@PlanningVariable注解需要在具有@PlanningEntity注解的类中的属性上。它在没有该注解的父类或子类中被忽略。

也可以将注解添加在字段上:

1
2
3
4
5
6
7
8
java复制代码@PlanningEntity
public class Queen {
...

@PlanningVariable(valueRangeProviderRefs = {"rowRange"})
private Row row;

}

规划变量允许为空

默认情况下,一个初始化的规划变量不能为空,所以一个初始化的解决方案永远不会对其任何规划变量使用空。在一个其它用例中,这可能会产生反作用。例如:在进行任务分配时,我们宁愿让低优先级的任务不被分配,而不是把它们分配给一个超负荷的工人。

如果要允许一个初始化的规划变量为空,请将nullable设置为true

1
2
3
4
java复制代码@PlanningVariable(..., nullable = true)
public Worker getWorker() {
return worker;
}

ConstraintProvider约束流实现时,默认会过滤掉具有null规划变量的规划实体。使用fromUnfiltered()可以避免这种情况。

1
2
3
4
java复制代码private UniConstraintStream<RockShow> getShowWithoutDate(ConstraintFactory constraintFactory) {
return constraintFactory.fromUnfiltered(RockShow.class)
.filter(rockShow -> rockShow.getDate() == null);
}

OptaPlanner会自动将数值null添加到数值范围中。无需在一个由ValueRangeProvider提供的List中添加null

使用一个允许为null的规划变量意味着我们的分数计算要负责惩罚(甚至奖励)有null值的变量。

总结

这一篇章主要学习了OptaPlanner的Problem Fact和Planning Entity以及Planning Variable的概念,及如何在OptaPlanner去使用它。

作业

大家可以回过头来,看看之前的两个例子,来加深对这些概念的理解,这三个概念是贯穿整个OptPlanner最核心的三个概念,一定要真正的去掌握和弄懂。

结束语

下一篇章,我们会着重讲解Planning variable规划变量的其它特性,为什么没有放在这里一起讲解,是因为下一篇章内容较多且需要有一定的基础才能理解,所以在大家学习完这三个重要的概念后,再来学习它的其它特性。

创作不易,禁止未授权的转载。如果我的文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧❤❤❤❤❤❤

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%