每个@PlanningSolution类都有一个分数。分数是比较两种解决方案的客观方法。得分越高的解越好。求解器的目标是找到所有可能解中得分最高的解。最佳解是求解器在求解过程中遇到的得分最高的解,这可能是最优解。
OptaPlanner不能自动知道哪个解决方案最适合你的业务,所以你需要告诉它如何根据你的业务需求计算给定@PlanningSolution实例的分数。如果您忘记或无法实现重要的业务约束,那么解决方案可能是无用的:
要实现口头业务约束,需要将其形式化为分数约束。幸运的是,在OptaPlanner中定义约束是非常灵活的,通过以下得分技术:
- 分数符号(正或负):最大化或最小化约束类型
- 得分权重:将成本/利润放在约束类型上
- 评分级别(硬、软、…):对一组约束类型进行优先排序
- 帕累托评分法(很少使用)
花点时间熟悉前三种技巧。一旦理解了它们,将大多数业务约束形式化就变得很简单了。
不要假设你的公司事先知道所有的分数限制。 期待在第一次发行后添加,更改或删除分数限制。 |
所有得分技术都是基于约束条件。约束可以是简单的模式(例如解决方案中的最大化苹果收成),也可以是更复杂的模式。积极约束是你想要最大化的约束。负约束是你想要最小化的约束
上图表明,无论约束条件是正的还是负的,最优解总是得分最高。
大多数规划问题只有负约束,因此得分为负。在这种情况下,得分是被打破的负约束的权重之和,满分为0。例如,在n皇后中,得分是可以互相攻击的皇后对数量的负数。
消极约束和积极约束可以结合在一起,即使是在相同的分数水平中。
当约束在某个规划实体集上激活时(因为破坏了负约束或实现了正约束),它被称为约束匹配。
并非所有分数限制都同样重要。如果打破一个约束与打破另一个约束x次一样糟糕,那么这两个约束就具有不同的权重(但它们处于相同的得分水平)。例如,在车辆路由中,您可以使一个不高兴的驾驶员约束匹配的计数与两个油箱使用约束匹配的计数相同。
分数加权在用例中很容易,因为你可以给所有东西贴上价格标签。在这种情况下,积极约束使收入最大化,消极约束使费用最小化,因此它们共同使利润最大化。另外,分数加权也经常用于创造社会公平。例如,一名护士要求休假一天,她在新年前夜支付的体重比平时要高。
约束匹配的权重可以依赖于所涉及的规划实体。例如,在云平衡中,活动计算机的软约束匹配的权重是该计算机的维护成本(每台计算机不同)。
对约束施加适当的权重通常是一项困难的分析决策,因为这涉及到对其他约束做出选择和权衡。不同的利益相关者有不同的优先级。不要在实现开始时浪费时间讨论约束权重,而是添加约束配置并允许用户通过UI更改它们。不准确的权重比普通算法的破坏性要小:
大多数用例使用int权重的Score,例如HardSoftScore。
有时候一个分数限制比另一个分数限制更重要,不管后者被打破了多少次。在这种情况下,这些分数限制处于不同的级别。例如,护士不能同时做两班(由于物理现实的限制),所以这比所有护士幸福的限制都重要。
大多数用例只有两个分数级别,硬的和软的。两个分数的水平按字典顺序进行比较。首先比较第一个分数级别。如果这些不同,则忽略其余的分数水平。例如,打破0个硬约束和1000000个软约束的分数比打破1个硬约束和0个软约束的分数好。
如果有两个(或更多)分数级别,例如HardSoftScore,那么在没有打破硬约束的情况下,分数是可行的。
默认情况下,OptaPlanner将始终为所有规划变量分配一个规划值。 如果没有可行的解决方案,这意味着最好的解决方案将是不可行的。 为了代替保留一些未分配的规划实体,应用过度约束的规划。 |
对于每个约束,您需要选择一个分数级别、一个分数权重和一个分数符号。例如:-1soft,评分等级为soft,权重为1,签字数为负。当你的企业想要不同的分数水平时,不要使用很大的约束权重。这种被称为分数折叠的方法已经被打破了:
您的业务可能会告诉您,您的硬约束都具有相同的权重, 因为它们无法被打破(因此权重无关紧要)。 这是不正确的,因为如果特定数据集不存在可行的解决方案, 那么最不可行的解决方案允许业务估计他们缺少多少业务资源。 例如在云计算平衡中,需要购买多少台新计算机。 此外,它可能会造成一个分数陷阱。 例如,在云平衡中,如果一台计算机有七个CPU太少, 那么它的权重必须是只有一个CPU太少的情况下的七倍。 |
也支持三个或更多的分数级别。例如:一家公司可能认为利润比员工满意度更重要(反之亦然),而两者都受到现实条件的限制。
为了对公平性或负载平衡进行建模, 不需要使用大量的分数级别(尽管OptaPlanner可以处理许多分数级别)。 |
大多数用例使用带有两个或三个权重的Score,例如HardSoftScore和HardMediumSoftScore。
不太常见的是帕累托优化,它也被称为多目标优化。在帕累托评分中,分数约束处于相同的分数水平,但它们并不相互加权。当比较两个分数时,每个分数约束都单独比较,具有最主要分数约束的分数获胜。帕累托评分甚至可以与分数水平和分数约束加权相结合。
考虑这个有正约束的例子,我们想要得到最多的苹果和橙子。既然不能比较苹果和橘子,我们就不能对它们进行权衡。然而,尽管我们不能比较它们,我们可以说两个苹果比一个苹果好。同样地,我们可以说两个苹果和一个橙子比只有一个橙子好。因此,尽管我们无法最终比较某些分数(此时我们声明它们相等),但我们可以找到一组最优分数。这些被称为帕累托最优。
分数通常被认为是相等的。它留给人类从OptaPlanner找到的一组最佳解决方案(分数相等)中选择更好的。在上面的例子中,用户必须在解决方案A(三个苹果和一个橘子)和解决方案B(一个苹果和六个橘子)之间做出选择。可以保证OptaPlanner没有找到另一个有更多苹果或更多橙子的解决方案,甚至两者的更好组合(如两个苹果和三个橙子)。
OptaPlanner目前不支持帕累托评分。
一个pareto Score的compareTo方法是不可传递的,因为它做了一个 pareto比较。例如:有两个苹果比一个苹果大。 一个苹果等于一个橘子。然而,两个苹果并不比一个橙子大(但实际上是相等的)。 Pareto比较违反了接口java.lang的约定。可比的compareTo方法, 但规划师系统是帕累托比较安全的,除非在本文档中另有明确说明。 |
以上提到的所有得分技术,都可以无缝结合:
分数由score接口表示,它自然地扩展了Comparable:
要使用的Score实现取决于您的用例。您的分数可能无法有效地放入单个长值中。OptaPlanner有几个内置的Score实现,但是你也可以实现一个自定义的Score。大多数用例倾向于使用内置的HardSoftScore。
所有的Score实现也有一个initScore(这是一个int)。它主要用于OptaPlanner的内部使用:它是未初始化的规划变量的负数。从用户的角度来看,这是0,除非Construction Heuristic在初始化所有规划变量之前被终止(在这种情况下Score.isSolutionInitialized()返回false)。
Score实现(例如HardSoftScore)在整个求解器运行时中必须是相同的。Score实现在解决方案域类中配置:
避免使用浮点数或双精度计算分数。请使用BigDecimal或缩放后的long。
浮点数(float和double)不能正确表示十进制数。例如:double类型不能正确保存值0.05。相反,它保存最近的可表示值。浮点数的算术(包括加法和减法),特别是对于规划问题,会导致错误的决策:
此外,浮点数加法不是关联的:
这就导致了分数。
十进制数(BigDecimal)没有这些问题。
BigDecimal算术比int、long或double算术慢得多。在实验中, 我们发现计算分数的时间要长五倍。 因此,在许多情况下,将单个分数权重的所有数字乘以10的复数是值得的, 因此分数权重适合缩放成int或long。例如,如果我们将所有权重乘以1000, 则0.07的fuelCost变成70的fuelCostMillis,并且不再使用十进制分数权重。 |
根据需要的分数等级数量和分数权重类型,选择得分类型。大多数用例使用HardSoftScore。
要正确地将Score写入数据库(使用JPA/Hibernate) 或XML/JSON(使用JAXB/Jackson),请参阅集成章节。 |
SimpleScore只有一个int值,例如-123。它只有一个分数等级。
这种分数类型的变体:
- SimpleLongScore使用一个长值而不是int值。
- SimpleBigDecimalScore使用BigDecimal值而不是int值。
HardSoftScore有一个硬整型值和一个软整型值,例如-123hard/-456soft。它有两个分数级别(硬和软)。
这种分数类型的变体:
- HardSoftLongScore使用长值而不是整型值。
- HardSoftBigDecimalScore使用BigDecimal值而不是int值。
HardMediumSoftScore,有硬整型值、中整型值和软整型值,例如-123hard/-456medium/-789soft。它有三个分数等级(硬、中、软)。硬级别确定解决方案是否可行,中等级别和软级别评分值确定解决方案满足业务目标的程度。无论软值如何,较高的中间值优先于软值。
这种分数类型的变体:
- HardMediumSoftLongScore使用长值而不是整型值。
- HardMediumSoftBigDecimalScore使用BigDecimal值而不是int值。
BendableScore具有可配置的分数级别数量。它有一个硬int值数组和一个软int值数组,例如有两个硬级别和三个软级别,分数可以是[-123/-456]hard/[-789/-012/-345]soft。在这种情况下,它有五个分数等级。如果所有硬关卡至少为零,则该解决方案是可行的。
具有一个硬级别和一个软级别的BendableScore相当于HardSoftScore,而具有一个硬级别和两个软级别的BendableScore相当于HardMediumSoftScore。
需要在编译时设置硬分数和软分数级别的数量。在解决过程中改变是不灵活的。
不要仅仅因为你有7个约束就使用带有7个关卡的BendableScore。 为每个约束使用不同的评分级别是非常罕见的 一个约束匹配甚至超过了软1上的一百万个约束匹配。 通常,多个约束共享同一级别,并且相互加权。使用解释分数来获得同一关卡中 单个约束的权重。 |
这种分数类型的变体:
- BendableLongScore使用长值而不是整型值。
- BendableBigDecimalScore使用BigDecimal值而不是int值。
有几种方法可以计算一个解决方案的得分:
- 简单的Java分数计算:在Java(或其他JVM语言)的单个方法中一起实现所有约束。不能扩展。
- 约束流得分计算:在Java(或其他JVM语言)中将每个约束实现为单独的约束流。快速且可扩展。
- 增量Java分数计算(不推荐):在Java(或其他JVM语言)中实现多个低级方法。快速且可扩展。很难实现和维护。
- 流口水分数计算(已弃用):在DRL中将每个约束实现为单独的分数规则。可伸缩的。
每个分数计算类型都可以使用任何分数定义(如HardSoftScore或HardMediumSoftScore)。所有的分数计算类型都是面向对象的,可以重用现有的Java代码。
分数计算必须是只读的。不得以任何方式改变规划实体或问题事实。 例如,它不能在分数计算中调用计划实体上的setter方法。 OptaPlanner如果能够预测解决方案的得分, 则不会重新计算得分(除非启用了environmentMode断言)。 例如,在完成获胜步骤后,不需要计算分数,因为该操作之前已经完成和取消了。 因此,不能保证在分数计算过程中应用的更改实际发生。 若要在规划变量更改时更新规划实体,请使用阴影变量。 |
一个简单的方法来实现你的分数计算在Java。
- 优点:
- 普通的Java:没有学习曲线
- 将分数计算委托给现有代码库或遗留系统的机会
- 缺点:
- 慢
- 没有缩放,因为没有增量分数计算
实现接口EasyScoreCalculator的一个方法:
例如在n queens中:
在求解器配置中配置它:
要在求解器配置中动态配置EasyScoreCalculator的值(以便Benchmarker可以调整这些参数),添加easyScoreCalculatorCustomProperties元素并使用自定义属性:
一种在Java中实现分数计算增量的方法。
- 优点:
- 非常快,可扩展
- 如果实现正确,目前是最快的
- 非常快,可扩展
- 缺点:
- 很难写
- 可伸缩的实现大量使用映射、索引等(Drools规则引擎可以为您做的事情)。
- 你必须自己学习、设计、编写和改进所有这些性能优化
- 难以阅读
- 定期更改分数约束可能导致较高的维护成本
- 很难写
实现接口IncrementalScoreCalculator的所有方法:
例如在n queens中:
在求解器配置中配置它:
一段增量分数计算器代码可能很难编写和审查。 通过使用EasyScoreCalculator来实现由environmentMode触发的断言,从而断言其正确性。 |
要在求解器配置中动态配置IncrementalScoreCalculator的值(以便benchmark可以调整这些参数),请添加incrementalScoreCalculatorCustomProperties元素并使用自定义属性:
5.3.3.1. ConstraintMatchAwareIncrementalScoreCalculator
可选地,也实现ConstraintMatchAwareIncrementalScoreCalculator接口:
- 通过使用scoreexplain . getconstraintmatchtotalmap()将每个分数约束拆分来解释分数。
- 使用scoreexplain . getindictmentmap()通过每个实体打破多少约束来可视化或排序规划实体。
- 如果IncrementalScoreCalculator在FAST_ASSERT或FULL_ASSERT环境模式中损坏,则接收详细的分析。
例如,在机器重分配中,为每个约束类型创建一个ConstraintMatchTotal,并为每个约束匹配调用addConstraintMatch():
getConstraintMatchTotals()代码经常重复一些常规IncrementalScoreCalculator方法的逻辑。约束流和Drools分数计算没有这个缺点,因为它们在需要时自动识别约束匹配,而不需要任何额外的特定于领域的代码。
InitializingScoreTrend指定随着越来越多的变量被初始化(而已经初始化的变量不改变),分数将如何变化。如果有这样的信息,一些优化算法(如构造启发式和穷举搜索)会运行得更快。
对于分数(或单独的每个分数水平),指定趋势:
- ANY(默认值):初始化一个额外的变量可以积极或消极地改变分数。不提供任何性能增益。
- ONLY_UP(罕见):初始化一个额外的变量只能积极地改变分数。意味着:
- 只有积极的约束
- 并且初始化下一个变量不能取消与前一个初始化变量匹配的正约束。
- ONLY_DOWN:初始化一个额外的变量只能消极地改变分数。意味着:
- 只有负约束
- 并且初始化下一个变量不能取消与前一个初始化变量匹配的负约束。
大多数用例只有负面约束。其中许多都有一个InitializingScoreTrend,只会下降:
或者,您也可以单独指定每个分数级别的趋势:
当您将environmentMode置于FULL_ASSERT(或FAST_ASSERT)中时,它将在增量分数计算中检测分数损坏。但是,这并不能验证您的分数计算器是否按照您的业务需求实际实现了分数约束。例如,一个约束可能始终匹配错误的模式。为了验证独立实现的约束,配置assertionScoreDirectorFactory:
这样,NQueensConstraintProvider的实现就会被EasyScoreCalculator验证。
这可以很好地隔离分数损坏,但是要验证约束是否实现了真正的业务需求, 使用ConstraintVerifier进行单元测试通常会更好。 |
求解器通常会将大部分执行时间用于运行分数计算(这在其最深的循环中被调用)。更快的分数计算将在更短的时间内使用相同的算法返回相同的解决方案,这通常意味着在相同的时间内获得更好的解决方案。
在解决了一个问题后,求解器将记录每秒的计算速度。这是一个很好的衡量分数计算性能的方法,尽管它受到非分数计算执行时间的影响。它取决于问题数据集的问题规模。通常,即使对于高规模问题,它也高于1000,除非您使用的是EasyScoreCalculator。
在提高你的分数计算时,重点是最大限度地提高分数计算速度, 而不是最大限度地提高最好的分数。分数计算方面的重大改进, 只会产生很少或根本没有最佳分数改进,例如当算法陷入局部或全局最优时。 如果你关注的是计算速度,那么分数计算的改进就更加明显了。 此外,观察计算速度允许您删除或添加分数限制,并将其与原始计算速度进行比较。 将最佳分数与原版的最佳分数进行比较是毫无意义的:这是在比较苹果和橘子。 |
当解决方案发生更改时,增量分数计算(又名基于增量的分数计算)会计算前一个状态的增量,以找到新的分数,而不是在每个解决方案评估时重新计算整个分数。
例如,当一个女王a从第1行移动到第2行时,它不会检查女王B和C是否可以互相攻击,因为它们都没有改变:
同样,在员工名册上:
这是一个巨大的性能和可伸缩性增益。约束流或Drools分数计算为您提供了巨大的可伸缩性增益,而无需强制您编写复杂的增量分数计算算法。让规则引擎来做最困难的工作。
注意,加速与计划问题的大小(n)有关,这使得增量分数计算更具可伸缩性。
不要在你的分数计算中调用远程服务(除非你将EasyScoreCalculator连接到一个遗留系统)。网络延迟会降低你的分数计算性能。如果可能的话,缓存这些远程服务的结果。
如果约束的某些部分可以在求解器启动时计算一次,并且在求解过程中从不更改,则将它们转换为缓存的问题事实。
如果你知道某个约束永远不可能被打破(或者它总是被打破),那么就不要为它编写分数约束。例如,在n个皇后中,分数计算不会检查是否有多个皇后占据同一列,因为皇后的列永远不会改变,并且每个解决方案从每个皇后在不同列开始。
不要做得太过火了。如果一些数据集没有使用特定的约束, 而其他数据集使用,那么只要尽快从约束中返回即可。 没有必要根据数据集动态更改您的分数计算。 |
有时可以内置硬约束,而不是实现硬约束。例如,如果讲座A永远不应该分配给房间X,但它在解决方案上使用ValueRangeProvider,那么求解器也会经常尝试将其分配给房间X(只是发现它打破了一个硬约束)。在规划实体或筛选选择上使用ValueRangeProvider来定义课程a应该只分配一个不同于X的房间。
这可以在某些用例中提供良好的性能增益,不仅因为分数计算更快,而且主要是因为大多数优化算法将花费更少的时间来评估不可行的解决方案。然而,这通常不是一个好主意,因为这确实存在以短期利益换取长期损害的风险:
- 许多优化算法依赖于在改变规划实体时打破硬约束的自由,以摆脱局部最优。
- 这两种实现方法都有局限性(特性兼容性、禁用自动性能优化),在它们的文档中有解释。
- 验证您的分数计算是在正确的数字类型中进行的。如果要对int值求和,不要将其求和为双精度类型,这会花费更长的时间。
- 为了获得最佳性能,始终使用服务器模式(java -server)。通过开启服务器模式,我们发现性能提高了50%。
- 为了获得最佳性能,请使用最新的Java版本。例如,在过去,通过从java 1.5切换到1.6,我们看到性能提高了30%。
- 永远记住,过早的优化是万恶之源。确保你的设计足够灵活,允许基于配置的调整。
确保你的分数限制不会导致分数陷阱。受困分数约束对不同的约束匹配使用相同的权重,其实它可以很容易地使用不同的权重。它有效地将约束匹配集中在一起,这为该约束创建了一个平线得分函数。这可能导致一种解决方案状态,在这种状态下,需要进行多次移动才能解决或降低单个约束的权重。分数陷阱的一些例子:
- 每张桌子需要两名医生,但每次只移动一名医生。所以求解者没有动机把医生移到没有医生的桌子上。在score函数的分数约束中,惩罚一个没有医生的表,而不是一个只有一个医生的表。
- 两个考试需要同时进行,但你一次只移动一个考试。所以求解器必须将其中一个考试移到另一个时间段,而不同时移动另一个。添加一个粗粒度移动,可以同时移动两个考试。
例如,考虑这个分数陷阱。如果蓝色物品从超载的电脑移到空的电脑,那么硬分数应该会提高。陷阱分数的实现无法做到这一点:
求解器最终应该会摆脱这个陷阱,但这需要付出很大的努力(尤其是在超载的计算机上有更多进程的情况下)。在他们这样做之前,他们可能实际上开始将更多的进程移到过载的计算机中,因为这样做不会受到惩罚。
避免分数陷阱并不意味着你的分数函数应该足够聪明以避免局部最优。 让优化算法来处理局部最优。 避免分数陷阱意味着对每个分数约束单独避免一个平坦的分数函数。 | |
一定要说明不可行的程度。企业通常会说:“如果解决方案不可行, 不管它有多不可行。”虽然这对业务来说是正确的,但对分数计算来说并非如此, 因为它从知道它有多不可行中受益。在实践中, 软约束通常会自然而然地做到这一点,而硬约束也需要这样做。 |
有几种方法可以处理分数陷阱:
- 改进分数约束以区分分数权重。例如,对每个丢失的CPU惩罚-1hard,而不是只在任何CPU丢失时惩罚-1hard。
- 如果从业务角度来看不允许更改分数约束,则添加一个带有分数约束的较低分数级别,以进行区分。例如,对于每个丢失的CPU,惩罚-1subsoft,如果任何CPU丢失,则在-1hard之上。业务忽略子软评分级别。
- 添加粗粒度的移动,并将它们与现有的细粒度移动联合选择。粗粒度的移动可以有效地进行多次移动,从而通过一次移动直接摆脱分数陷阱。例如,将多个项目从同一个容器移动到另一个容器。
并非所有分数约束都具有相同的性能成本。有时,一个分数约束会彻底破坏分数计算性能。使用Benchmarker进行一分钟的运行,检查如果注释掉除一个分数约束外的所有分数约束,那么分数计算速度会发生什么变化。
一些用例有提供公平时间表的业务需求(通常作为软分数约束),例如:
- 在员工之间公平分配工作量,避免嫉妒。
- 在资产之间均匀分配工作负载,以提高可靠性。
实现这样的约束可能看起来很困难(特别是因为有不同的方法来形式化公平性),但通常工作负载的平方实现是最理想的。对于每个员工/资产,计算工作量w并从得分中减去w²。
如上所示,平方工作负载实现保证,如果您从给定的解决方案中选择两名员工,并使他们在这两名员工之间的分配更公平,那么最终的新解决方案将具有更好的总体得分。不要只使用与平均工作负载的差异,因为这会导致不公平,如下所示。
除了工作负载的平方之外,还可以使用方差(平均值的平方差)或 标准差(方差的平方根)。这对分数比较没有影响,因为在计划期间平均值不会改变。 它只是需要更多的工作来实现(因为需要知道平均值), 并且稍微慢一些(因为计算时间更长一些)。 |
当工作量完美平衡时,用户通常喜欢看到0分,而不是上图中分散注意力的-34soft(对于几乎完美平衡的最后一个解决方案)。要消除这种情况,可以将平均值乘以实体数量添加到分数中,或者在UI中显示方差或标准偏差。
为每个约束确定正确的权重和级别并不容易。它通常涉及与不同的利益相关者及其优先事项进行谈判。此外,对业务经理来说,量化软约束的影响通常是一种新的体验,因此他们需要多次迭代才能做到正确。
不要陷入进退两难的境地。提供一个UI来调整约束权重并可视化最终的解决方案,以便业务经理可以自己调整约束权重:
首先,创建一个新类来保存约束权重和其他约束参数。用@ConstraintConfiguration注释它:
每个规划解决方案将有一个该类的实例。规划解决方案和约束配置具有一对一的关系,但是它们服务于不同的目的,因此它们没有合并到单个类中。@ConstraintConfiguration类可以扩展父类@ConstraintConfiguration,这在具有许多区域约束的国际用例中很有用。
在规划解决方案中添加约束配置,并使用@ConstraintConfigurationProvider注释该字段或属性:
@ConstraintConfigurationProvider注释会自动将约束配置公开为问题事实,不需要添加@ProblemFactProperty注释。
约束配置类保存约束权重,但它也可以保存约束参数。例如,在会议调度中,最小暂停约束有一个约束权重(与任何其他约束一样),但它也有一个约束参数,用于定义同一发言者的两次发言之间的最小暂停长度。暂停的时间长短取决于会议的安排:在一些大型会议中,从一个房间到另一个房间,20分钟是不够的。该暂停长度是约束配置中的一个字段,没有@ConstraintWeight注释。
在约束配置类中,为每个约束添加@ConstraintWeight字段或属性:
约束权重的类型必须与规划解决方案的得分成员的得分类相同。例如,在会议调度中,conferencessolution . getscore()和ConferenceConstraintConfiguration.getSpeakerConflict()都返回HardMediumSoftScore。
约束权重不能为空。给每个约束权重一个默认值,但是在UI中公开它们,以便业务用户可以调整它们。上面的例子使用了ofHard(), ofMedium()和ofSoft()方法来做到这一点。注意,它将内容冲突约束默认为比主题轨道冲突约束重要十倍。通常,约束权重只使用一个分数级别,但也可以使用多个分数级别(以较小的性能代价)。
每个约束都有一个约束包和一个约束名,它们一起构成约束id。它们将约束权重与约束实现连接起来。对于每个约束权重,必须有一个具有相同包和相同名称的约束实现。
- @ConstraintConfiguration注释有一个constraintPackage属性,默认为约束配置类的包。使用约束流的情况通常不需要指定它。使用Drools分数计算(已弃用)的情况可能需要覆盖它,因为drl通常使用不同的包。
- @ConstraintWeight注释有一个约束名称的值(例如“Speaker conflict”)。它从@ConstraintConfiguration继承约束包,但它可以覆盖它,例如@ConstraintWeight(constraintPackage = "…region.france",…)使用不同于其他权重的约束包。
因此,每个约束权重都以约束包和约束名称结束。每个约束权重链接到一个约束实现,例如在约束流中:
每个约束权重定义了其约束的分数级别和分数权重。约束实现调用rewardConfigurable()或penalizeConfigurable(),并自动应用约束权重。
如果约束实现提供匹配权值,则该匹配权值与约束权值相乘。例如,内容冲突约束权重默认为100soft,约束实现根据共享内容标签的数量和两个对话的重叠持续时间对每个匹配进行惩罚:
因此,当两个重叠的演讲只共享一个内容标签并且重叠60分钟时,得分受到-6000软的影响。但是当2个重叠的谈话共享3个内容标签时,匹配权重为180,因此得分受到-18000软的影响。
在开发过程中解释分数的最简单方法是打印getSummary()的返回值,但仅将该方法用于诊断目的:
例如,在会议调度中,这打印了谈话S51负责打破演讲者所需房间标签的硬约束:
不要试图解析这个字符串,也不要在UI或公开的服务中使用它。 相反,请使用下面的ConstraintMatch API并正确执行。 |
在上面的字符串中,有两个先前未解释的概念。
justification是用户定义的对象,它实现了org. optaplaner .core.api.score.stream. constraintjustification接口,它携带有关约束匹配的有意义的信息,比如它的包、名称和分数。
另一方面,起诉对象是直接导致约束匹配的对象。例如,如果您的约束惩罚每辆车,那么每辆车将有一个org.optaplanner.core.api.score.constraint. prosecution实例,将车辆作为起诉对象。起诉书通常用于热图可视化。
如果应用程序的其他部分(例如web)需要计算解决方案的分数,请使用SolutionManager API:
然后当你需要计算一个解决方案的分数时使用它:
此外,scoreexplain可以通过约束匹配总数和/或起诉来帮助解释分数:
每个约束可以由不同的ConstraintJustification实现来证明,但是您也可以选择在约束之间共享它们。要接收所有类型的约束论证,调用:
List<ConstraintJustification> ConstraintJustification = scoreexplain . getjustification ();…
在score DRL中,证明总是org.optaplanner.core.api.score.stream的实例。DefaultConstraintJustification,而在约束流中,返回类型可以自定义,这样它就可以很容易地序列化并通过网络发送。这样的自定义理由可以像这样查询:
List<MyConstraintJustification> constraintjustification = scoreexplanation . getjustification . List(MyConstraintJustification.class);…
为了分解每个约束的分数,从scoreexplain中获取ConstraintMatchTotals:
每个ConstraintMatchTotal表示一个约束,并具有总体得分的一部分。所有ConstraintMatchTotal.getScore()的总和等于总分。
约束流和Drools分数计算自动支持约束匹配,但是增量Java分数 计算需要实现一个额外的接口。 |
要在UI中显示热图,突出显示对得分有影响的规划实体和问题事实,请从ScoreExplanation获取起诉书图:
Map<Object,起诉书<HardSoftScore>> indictmentMap = scoreexplain . getindictmentmap ();for (CloudProcess process: cloudBalance.getProcessList()){起诉书<HardSoftScore>起诉书=起诉书map .get(process);如果(起诉== null){继续;} //该规划实体的分数影响HardSoftScore totalScore = indicment . getscore ();for (ConstraintMatch<HardSoftScore> ConstraintMatch: indicment . getconstraintmatchset ()) {String constraintName = ConstraintMatch . getconstraintname ();HardSoftScore score = constraintMatch.getScore();…}}
每项起诉书都是该辩护对象所涉及的所有约束的总和。所有indicments . getscoretotal()的总和与总分不同,因为多个indicments可以共享相同的ConstraintMatch。
约束流和Drools分数计算自动支持约束匹配,但是增量Java分数计 算需要实现一个额外的接口。 |
建议为每个分数约束单独编写一个单元测试,以检查其行为是否正确。不同的分数计算类型有不同的测试工具。有关更多信息,请参见测试约束流或测试流约束。