这是我参与更文挑战的第5天,活动详情查看: 更文挑战
内容概要
今天我们来学习一个如何建立一个业务模型,有哪些概念是需要我们掌握的,例如:ProblemFact、PlanningEntity、ProblemSolution,以及这些对象中的字段有什么作用。
如何区分ProblemFact和PlanningEntity
我们来看一下规划问题的数据,会认识到其中的对象,每一个都可以被归类为以下的一种:
- 无任何关联的类:不被任何分数约束所使用,从规划的角度来看,这个数据是垃圾数据。
- Problem Fact事实类:由分数约束使用,但在求解期间不改变(只要问题保持不变)。比如说。床位、房间、班次、人员、时段….问题事实类的所有属性都是问题属性。
- Planning Entity规划实体类:由分数约束使用,在求解期间改变。比如说。
CloudProcess
,TimeTable
,Exam
, ……在求解过程中改变的属性是Planning Variable规划变量,其他属性是问题属性。
问问自己,在求解期间,什么类会发生变化?哪个类有我想让求解器为我改变的变量?这个类就是一个Planning Entity规划实体。大多数用例只有一个规划实体类,大多数用例的每个规划实体类也只有一个规划变量。
在OptaPlanner中,所有Problem Fact
和Planning Entity
都是普通的JavaBeans(POJOs)。
Problem Fact
Problem Fact
问题事实类是任何带有getter
方法的JavaBean(POJO),在求解过程中不会改变。例如,云资源优化例子中,Computer
是Problem Fact问题事实。
1 | java复制代码@XStreamAlias("CloudComputer") |
当然,一个问题事实可以参包含他问题事实。
1 | java复制代码public class Course { |
一个问题事实类不需要任何OptaPlanner的注解。
Planning entity
Planning entity annotation
Planning Entity规划实体
是一个JavaBean(POJO),在求解过程中会发生变化,例如一个CloudProcess
会换成另一台Computer
。一个规划问题有多个规划实体,例如对于云资源优化问题,每个CloudProcess
都是一个规划实体。但通常只有一个规划实体类,例如CloudProcess类
。
一个规划实体类,需要增加@PlanningEntity
注解。
每个规划实体类有一个或多个Planning Variable规划变量
。它还应该有一个或多个定义性的属性。例如,CloudProcess
类中,computer
作为一个规划变量,在求解过程中会发生改变,而requiredCpuPower
,requiredMemory
,requiredNetworkBandwidth
作为一个线程的其它信息,求解过程中不会发生改变。
1 | java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class) |
一个规划实体类可以有多个规划变量。例如课程时间表例子,一节课程,是有老师在某个时间段上在某个教室进行授课,所以Lesson
它有两个规划变量,timeslot
时段和room
教室。
1 | java复制代码@PlanningEntity |
求解器配置需要声明每个规划实体类。
1 | xml复制代码<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
Planning Entity比较器
一些优化算法如果能估算出哪些规划实体更难规划,就能更有效地工作。例如:在垃圾箱包装中,更大的物品更难装入,在课程安排中,有更多学生的讲座更难安排,而在n个皇后中,中间的皇后更放在棋盘上。
不要尝试使用Comparator比较器来实现业务约束。它不会影响评分函数:如果我们有无限的求解时间,返回的解决方案将是一样的。
为了达到某些实体在日程表中被安排得更早的目的,添加一个分数约束来改变分数函数,使其更倾向于这种解决方案。只有在可以使求解器更有效率的情况下,才考虑也增加规划实体的Comparator。
为了让启发式方法能够利用当前优化问题的特殊信息,需要@PlanningEntity
注解设置 difficultyComparatorClass
属性。
1 | java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class) |
1 | java复制代码public class CloudProcessDifficultyComparator implements Comparator<CloudProcess> { |
比较器应该从高到低实现:容易的实体比较低,困难的实体比较高。例如,在垃圾箱包装中:小物品<中物品<大物品。
虽然大多数算法都是先从较难的实体开始,但它们只是把顺序颠倒过来。
针对云平衡CloudBalance
的例子,它的比较器就是将所需资源(cpuPower
、memory
、netWorkBandwidth
)更多的进程优先排在前面,让求解器可以优先解决这些需求比较大的进程。
当前的规划变量都不应该被用来比较规划实体的难度。在构建启发式方法期间,这些变量无论如何都可能是空的。例如,不应该使用CloudProcess
中的computer
。
Planning variable规划变量
Planning variable注解
Planning Variable
规划变量是Planning Entity
上的一个JavaBean属性(所以是一个getter
和setter
)。它指向一个规划值,该值在规划过程中会发生变化。例如,CloudProcess
的computer
属性就是一个真正的规划变量。高级情况下也可以有阴影变量,后续我们会学习到。
Planning Variable
规划变量getter
方法需要添加@PlanningVariable
注解,并且需要一个非空的valueRangeProviderRefs
属性。
1 | java复制代码@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class) |
valueRangeProviderRefs
属性定义了此规划变量的可能规划值。它引用了一个或多个@ValueRangeProvider id
。
@PlanningVariable注解需要在具有@PlanningEntity注解的类中的属性上。它在没有该注解的父类或子类中被忽略。
也可以将注解添加在字段上:
1 | java复制代码@PlanningEntity |
规划变量允许为空
默认情况下,一个初始化的规划变量不能为空,所以一个初始化的解决方案永远不会对其任何规划变量使用空。在一个其它用例中,这可能会产生反作用。例如:在进行任务分配时,我们宁愿让低优先级的任务不被分配,而不是把它们分配给一个超负荷的工人。
如果要允许一个初始化的规划变量为空,请将nullable
设置为true
。
1 | java复制代码@PlanningVariable(..., nullable = true) |
ConstraintProvider约束流实现时,默认会过滤掉具有null规划变量的规划实体。使用fromUnfiltered()可以避免这种情况。
1 | java复制代码private UniConstraintStream<RockShow> getShowWithoutDate(ConstraintFactory constraintFactory) { |
OptaPlanner会自动将数值null
添加到数值范围中。无需在一个由ValueRangeProvider
提供的List
中添加null
。
使用一个允许为null的规划变量意味着我们的分数计算要负责惩罚(甚至奖励)有null值的变量。
总结
这一篇章主要学习了OptaPlanner的Problem Fact和Planning Entity以及Planning Variable的概念,及如何在OptaPlanner去使用它。
作业
大家可以回过头来,看看之前的两个例子,来加深对这些概念的理解,这三个概念是贯穿整个OptPlanner最核心的三个概念,一定要真正的去掌握和弄懂。
结束语
下一篇章,我们会着重讲解Planning variable规划变量的其它特性,为什么没有放在这里一起讲解,是因为下一篇章内容较多且需要有一定的基础才能理解,所以在大家学习完这三个重要的概念后,再来学习它的其它特性。
创作不易,禁止未授权的转载。如果我的文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧❤❤❤❤❤❤
本文转载自: 掘金