2.9 动态调整约束权重

1,193 阅读4分钟

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

内容概要

上一篇章里,我们已经学习了分数计算的一些窍门和规避分数陷阱的一些方法。今天这一篇章我们来学习如何动态配置约束权重,方便我们在调整约束时做到动态支持。

为每个约束确定正确的权重和级别并不容易。因为各个约束之间存在优先级平衡的情况。此外,量化软约束的影响对我们来说也是一种新的体验,所以他们需要多次调整才能找到一个适合的权重和级别。

提供一个UI来调整约束条件的权重,并将求解的结果可视化,以便可以自己调整约束条件的权重。

parameterizeTheScoreWeights_edit.png

创建一个约束条件配置

首先,创建一个新的类来保存约束的权重和其他约束参数。用@ConstraintConfiguration注解。

@ConstraintConfiguration
public class ConferenceConstraintConfiguration {
    ...
}

每个PlanningSolution将有一个这个类的实例。PlanningSolutionConstraintConfig有一对一的关系,但它们有不同的目的,所以它们没有被合并成一个类。一个@ConstraintConfiguration类可以扩展一个父类@ConstraintConfiguration类,这在很多例子中是很有用的。

PlanningSolution上添加ConstraintConfig,并用@ConstraintConfigurationProvider在该字段或属性上添加注解。

@PlanningSolution
public class ConferenceSolution {

    @ConstraintConfigurationProvider
    private ConferenceConstraintConfiguration constraintConfiguration;
    
    // HOS = Hours of service (in terms of driving regulations)
    private long hosWeekDrivingSecondsBudget = 50L * 60L * 60L;
    private int hosWeekConsecutiveDrivingDaysBudget = 7;

    ...
}

@ConstraintConfigurationProvider注解自动将constraintConfiguration作为一个ProblemFact,所以不需要添加@ProblemFactProperty注解。

ConstraintConfiguration类持有Constraint的权重,但它也可以持有Constraint的参数。例如,在会议安排中,最小会议间隔时间约束有一个约束权重(像其他约束一样),但它也有一个约束参数,定义了同一发言人的两个演讲之间的最小间隔时间。这个间隔时间长度取决于会议:在一些大型会议中,20分钟还不够从一个房间走到另一个房间。这个间隔时间长度是约束配置中的一个field字段,没有@ConstraintWeight注解。

约束添加权重

ConstraintConfiguration类中,为每个约束添加一个@ConstraintWeight字段或属性。

@ConstraintConfiguration(constraintPackage = "...conferencescheduling.solver")
public class ConferenceConstraintConfiguration {

    @ConstraintWeight("Speaker conflict")
    private HardMediumSoftScore speakerConflict = HardMediumSoftScore.ofHard(10);

    @ConstraintWeight("Theme track conflict")
    private HardMediumSoftScore themeTrackConflict = HardMediumSoftScore.ofSoft(10);
    @ConstraintWeight("Content conflict")
    private HardMediumSoftScore contentConflict = HardMediumSoftScore.ofSoft(100);

    ...
}

ConstraintConfiguration的类型必须与PlanningSolutionScore相同的分数类。例如在会议安排例子中,ConferenceSolution.getScore()ConferenceConstraintConfiguration.getSpeakerConflict()都返回HardMediumSoftScore

一个ConstraintWeight不能为null。给每个ConstraintWeight一个默认值,但在界面中可以修改,以便对它们进行调整。上面的例子使用ofHard()·、ofMedium()ofSoft()方法来实现。

每个约束都有一个ConstraintPackage约束包和一个ConstraintName约束名称,它们共同构成了ConstraintID约束的ID。它们将约束的Weight与约束的实现联系起来。对于每个ConstraintWeight,必须有一个具有相同包和相同名称的约束实现

  • @ConstraintConfiguration 注解有一个 constraintPackage 属性,默认ConstraintConfiguration类的包。大多数使用Drools分数计算的情况下,需要覆盖这一点,因为DRL使用了另一个包。例如,下面的DRL使用了com... conferencescheduling.solver包,所以上面的约束配置指定了一个constraintPackage
  • @ConstraintWeight注解有一个值,是约束的名称(例如 "Speaker conflict")。它从@ConstraintConfiguration中继承约束包,但它可以覆盖它,例如@ConstraintWeight(constraintPackage = "...region.france", ...) 来使用与其他一些权重不同的约束包。

所以每个ConstraintWeight最后都有一个ConstraintPackage和一个ConstraintName。每个ConstraintWeight都与一个约束实现相联系,例如在Drools的分数计算中:

package ...conferencescheduling.solver;

rule "Speaker conflict"
    when
        ...
    then
        scoreHolder.penalize(kcontext);
end

rule "Theme track conflict"
    when
        ...
    then
        scoreHolder.penalize(kcontext, ...);
end

rule "Content conflict"
    when
        ...
    then
        scoreHolder.penalize(kcontext, ...);
end

每个ConstraintWeight都定义了其约束的ScoreLevelScoreWeight。约束的实现调用reward()policize(),约束的权重就会自动匹配。

如果约束实现提供了一个匹配权重,那么该MatchWeight匹配权重将与ConstraintWeight约束权重相乘。例如, content conflict 约束权重默认为100soft,约束实现根据共享内容标签的数量来惩罚每个匹配的结果:

 @ConstraintWeight("Content conflict")
    private HardMediumSoftScore contentConflict = HardMediumSoftScore.ofSoft(100);
rule "Content conflict"
    when
        $talk1 : Talk(...)
        $talk2 : Talk(...)
    then
        scoreHolder.penalize(kcontext,
                $talk2.overlappingContentCount($talk1));
end

因此,当2个冲突内容只共享1个内容标签时,得分会受到-100soft的影响。但是,当2个冲突内容共享3个内容标签时,匹配权重为3,所以分数会受到-300soft的影响。

总结

这一篇章我们学习了如何为每个约束增加权重和如何动态调整约束,这个很关键,因为在以往的经验中,对约束的权重调整是非常困难的,往往存在着多次调整的情况,所以约束支持动态调整权重就十分的关键了。

结束语

下一篇章,我们将来学习对如何查看每个约束的分值以及是如何计算的。

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