13.10. 制約の設定

各制約の正しい重みとレベルを決定するのは簡単ではありません。多くの場合、さまざまな利害関係者やその優先事項との交渉が必要になります。さらに、ソフト制約の影響を定量化することは、多くの場合、ビジネスマネージャーにとって新しい経験であるため、正しく理解するには何度も反復する必要があります。これを簡単にするには、制約の重みとパラメーターを指定して @ConstraintConfiguration クラスを使用します。そして、次の図に示すように、ビジネスマネージャーが制約の重みを自分で調整し、その結果得られるソリューションを視覚化できるように UI を提供します。

スコアの重みをパラメーター化する方法を示す図

たとえば、会議のスケジュール設定の問題では、最小一時停止制約には制約の重みがありますが、同じ話者による 2 つの会議時間の長さを定義する制約パラメーターもあります。一時停止の長さは会議によって異なります。一部の大規模な会議では、ある部屋から別の部屋に移動するのに 20 分では不十分ですが、小規模な会議では 10 分で十分な場合があります。一時停止の長さは、@ConstraintWeight アノテーションのない制約設定内のフィールドです。

各制約には制約パッケージと制約名があり、それらを合わせて制約 ID が形成されます。制約 ID は、制約の重みを制約の実装に結び付けます。制約の重みごとに、同じパッケージと同じ名前の制約実装が必要です。

  • @ConstraintConfiguration アノテーションには、制約設定クラスのパッケージをデフォルトとする constraintPackage プロパティーがあります。制約ストリームがある場合は、通常、それを指定する必要はありません。
  • @ConstraintWeight アノテーションには、制約名 (”Speaker conflict” など) の value があります。@ConstraintConfiguration から制約パッケージを継承しますが、たとえば @ConstraintWeight(constraintPackage = "…​region.france", …​) をオーバーライドして、他の重みとは異なる制約パッケージを使用することができます。

したがって、すべての制約の重みは、最終的に制約パッケージと制約名になります。各制約の重みは、制約ストリームなどで制約の実装とリンクします。

public final class ConferenceSchedulingConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory factory) {
        return new Constraint[] {
                speakerConflict(factory),
                themeTrackConflict(factory),
                contentConflict(factory),
                ...
        };
    }

    protected Constraint speakerConflict(ConstraintFactory factory) {
        return factory.forEachUniquePair(...)
                ...
                .penalizeConfigurable("Speaker conflict", ...);
    }

    protected Constraint themeTrackConflict(ConstraintFactory factory) {
        return factory.forEachUniquePair(...)
                ...
                .penalizeConfigurable("Theme track conflict", ...);
    }

    protected Constraint contentConflict(ConstraintFactory factory) {
        return factory.forEachUniquePair(...)
                ...
                .penalizeConfigurable("Content conflict", ...);
    }

    ...

}

各制約の重みは、その制約のスコアレベルとスコアの重みを定義します。制約の実装は、rewardConfigurable() または penalizeConfigurable() を呼び出し、制約の重みが自動的に適用されます。

制約の実装が一致の重みを提供する場合、その一致の重みは制約の重みと乗算されます。たとえば、コンテンツ競合 制約の重みはデフォルトで 100soft に設定されています。そして、制約の実装では、共有コンテンツタグの数と 2 つの会議の重複時間に基づいて各一致にペナルティが課されます。

    @ConstraintWeight("Content conflict")
    private HardMediumSoftScore contentConflict = HardMediumSoftScore.ofSoft(100);
Constraint contentConflict(ConstraintFactory factory) {
    return factory.forEachUniquePair(Talk.class,
        overlapping(t -> t.getTimeslot().getStartDateTime(),
            t -> t.getTimeslot().getEndDateTime()),
        filtering((talk1, talk2) -> talk1.overlappingContentCount(talk2) > 0))
        .penalizeConfigurable("Content conflict",
                (talk1, talk2) -> talk1.overlappingContentCount(talk2)
                        * talk1.overlappingDurationInMinutes(talk2));
}

したがって、2 つの重複する会議が 1 つのコンテンツタグのみを共有し、60 分重複する場合、スコアは -6000soft の影響を受けます。ただし、2 つの重複するトークが 3 つのコンテンツタグを共有する場合、一致の重みは 180 となるため、スコアは -18000soft の影響を受けます。

手順

  1. 制約の重みと他の制約パラメーターを保持する新しいクラス (ConferenceConstraintConfiguration など) を作成します。
  2. このクラスに @ConstraintConfiguration のアノテーションを付けます。

    @ConstraintConfiguration
    public class ConferenceConstraintConfiguration {
        ...
    }
  3. 計画ソリューションに制約設定を追加し、そのフィールドまたはプロパティーに @ConstraintConfigurationProvider のアノテーションを付けます。

    @PlanningSolution
    public class ConferenceSolution {
    
        @ConstraintConfigurationProvider
        private ConferenceConstraintConfiguration constraintConfiguration;
    
        ...
    }
  4. 制約設定クラスで、各制約の @ConstraintWeight プロパティーを追加し、それらの重みにデフォルト値を与えます。

    @ConstraintConfiguration(constraintPackage = "...conferencescheduling.score")
    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);
    
        ...
    }

    @ConstraintConfigurationProvider アノテーションは、制約設定を問題の事実として自動的に公開します。@ProblemFactProperty アノテーションを追加する必要はありません。制約の重みを null にすることはできません。

  5. ビジネスユーザーが値を調整できるように、制約の重みを UI に公開します。前述の例では、ofHard()ofMedium()、および ofSoft() メソッドを使用してこれを行います。コンテンツの競合 制約が テーマトラックの競合 制約よりも 10 倍重要であるとデフォルトで設定されていることに注目してください。通常、制約の重みでは 1 つのスコアレベルのみが使用されますが、(わずかなパフォーマンスコストで) 複数のスコアレベルを使用することも可能です。