From bb20e567e73cec94724282c1585eaf513c48d386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Mon, 22 Jan 2024 17:04:02 +0100 Subject: [PATCH] forEachIncludingUnassigned --- .../streams/bavet/BavetConstraintFactory.java | 2 +- .../bi/BavetAbstractBiConstraintStream.java | 4 +- .../BavetAbstractQuadConstraintStream.java | 4 +- .../tri/BavetAbstractTriConstraintStream.java | 4 +- .../bavet/uni/AbstractForEachUniNode.java | 6 +- .../uni/BavetAbstractUniConstraintStream.java | 4 +- .../uni/BavetForEachUniConstraintStream.java | 4 +- ...=> ForEachExcludingUnassignedUniNode.java} | 4 +- ...=> ForEachIncludingUnassignedUniNode.java} | 4 +- .../streams/common/RetrievalSemantics.java | 4 +- .../streams/bavet/BavetRegressionTest.java | 8 +- .../inliner/AbstractScoreInlinerTest.java | 2 +- .../api/score/stream/ConstraintFactory.java | 80 ++++++------------- .../score-calculation.adoc | 4 +- .../modeling-planning-problems.adoc | 2 +- ...onferenceSchedulingConstraintProvider.java | 2 +- .../MeetingSchedulingConstraintProvider.java | 26 +++--- ...ntAdmissionScheduleConstraintProvider.java | 14 ++-- 18 files changed, 73 insertions(+), 105 deletions(-) rename core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/{ForEachExcludingNullVarsUniNode.java => ForEachExcludingUnassignedUniNode.java} (88%) rename core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/{ForEachIncludingNullVarsUniNode.java => ForEachIncludingUnassignedUniNode.java} (73%) diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/BavetConstraintFactory.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/BavetConstraintFactory.java index b67b11af0a..43e586e31e 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/BavetConstraintFactory.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/BavetConstraintFactory.java @@ -87,7 +87,7 @@ private Predicate getNullityFilter(Class fromClass) { } @Override - public UniConstraintStream forEachIncludingNullVars(Class sourceClass) { + public UniConstraintStream forEachIncludingUnassigned(Class sourceClass) { assertValidFromType(sourceClass); return share(new BavetForEachUniConstraintStream<>(this, sourceClass, null, RetrievalSemantics.STANDARD)); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/bi/BavetAbstractBiConstraintStream.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/bi/BavetAbstractBiConstraintStream.java index 48b1179e19..cad97cd659 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/bi/BavetAbstractBiConstraintStream.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/bi/BavetAbstractBiConstraintStream.java @@ -115,7 +115,7 @@ public final BiConstraintStream ifExists(Class otherClass, TriJoine @Override public final BiConstraintStream ifExistsIncludingNullVars(Class otherClass, TriJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifExists(constraintFactory.fromUnfiltered(otherClass), joiners); } @@ -141,7 +141,7 @@ public final BiConstraintStream ifNotExists(Class otherClass, TriJo @Override public final BiConstraintStream ifNotExistsIncludingNullVars(Class otherClass, TriJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifNotExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifNotExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifNotExists(constraintFactory.fromUnfiltered(otherClass), joiners); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/quad/BavetAbstractQuadConstraintStream.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/quad/BavetAbstractQuadConstraintStream.java index 3c43b5288f..7d521f08d2 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/quad/BavetAbstractQuadConstraintStream.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/quad/BavetAbstractQuadConstraintStream.java @@ -93,7 +93,7 @@ public final QuadConstraintStream ifExists(Class otherClass, public final QuadConstraintStream ifExistsIncludingNullVars(Class otherClass, PentaJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifExists(constraintFactory.fromUnfiltered(otherClass), joiners); } @@ -121,7 +121,7 @@ public final QuadConstraintStream ifNotExists(Class otherClas public final QuadConstraintStream ifNotExistsIncludingNullVars(Class otherClass, PentaJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifNotExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifNotExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifNotExists(constraintFactory.fromUnfiltered(otherClass), joiners); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/tri/BavetAbstractTriConstraintStream.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/tri/BavetAbstractTriConstraintStream.java index 86c1decd16..2a3618c34c 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/tri/BavetAbstractTriConstraintStream.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/tri/BavetAbstractTriConstraintStream.java @@ -115,7 +115,7 @@ public final TriConstraintStream ifExists(Class otherClass, Quad public final TriConstraintStream ifExistsIncludingNullVars(Class otherClass, QuadJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifExists(constraintFactory.fromUnfiltered(otherClass), joiners); } @@ -143,7 +143,7 @@ public final TriConstraintStream ifNotExists(Class otherClass, Q public final TriConstraintStream ifNotExistsIncludingNullVars(Class otherClass, QuadJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifNotExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifNotExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifNotExists(constraintFactory.fromUnfiltered(otherClass), joiners); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/AbstractForEachUniNode.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/AbstractForEachUniNode.java index 2ef504f7e8..76413a3cc9 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/AbstractForEachUniNode.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/AbstractForEachUniNode.java @@ -13,14 +13,14 @@ /** * Filtering nodes are expensive. * Considering that most streams start with a nullity check on genuine planning variables, - * it makes sense to create a specialized version of the node for this case ({@link ForEachExcludingNullVarsUniNode}), - * as opposed to forcing an extra filter node on the generic case ({@link ForEachIncludingNullVarsUniNode}). + * it makes sense to create a specialized version of the node for this case ({@link ForEachExcludingUnassignedUniNode}), + * as opposed to forcing an extra filter node on the generic case ({@link ForEachIncludingUnassignedUniNode}). * * @param */ public abstract sealed class AbstractForEachUniNode extends AbstractNode - permits ForEachIncludingNullVarsUniNode, ForEachExcludingNullVarsUniNode { + permits ForEachIncludingUnassignedUniNode, ForEachExcludingUnassignedUniNode { private final Class forEachClass; private final int outputStoreSize; diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetAbstractUniConstraintStream.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetAbstractUniConstraintStream.java index cc26db27e0..9883f86d54 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetAbstractUniConstraintStream.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetAbstractUniConstraintStream.java @@ -120,7 +120,7 @@ public final UniConstraintStream ifExists(Class otherClass, BiJoiner UniConstraintStream ifExistsIncludingNullVars(Class otherClass, BiJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifExists(constraintFactory.fromUnfiltered(otherClass), joiners); } @@ -146,7 +146,7 @@ public final UniConstraintStream ifNotExists(Class otherClass, BiJoine @Override public final UniConstraintStream ifNotExistsIncludingNullVars(Class otherClass, BiJoiner... joiners) { if (getRetrievalSemantics() == RetrievalSemantics.STANDARD) { - return ifNotExists(constraintFactory.forEachIncludingNullVars(otherClass), joiners); + return ifNotExists(constraintFactory.forEachIncludingUnassigned(otherClass), joiners); } else { return ifNotExists(constraintFactory.fromUnfiltered(otherClass), joiners); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetForEachUniConstraintStream.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetForEachUniConstraintStream.java index 310a322612..ffdcfd59dc 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetForEachUniConstraintStream.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/BavetForEachUniConstraintStream.java @@ -48,8 +48,8 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { TupleLifecycle> tupleLifecycle = buildHelper.getAggregatedTupleLifecycle(childStreamList); int outputStoreSize = buildHelper.extractTupleStoreSize(this); - var node = filter == null ? new ForEachIncludingNullVarsUniNode<>(forEachClass, tupleLifecycle, outputStoreSize) - : new ForEachExcludingNullVarsUniNode<>(forEachClass, filter, tupleLifecycle, outputStoreSize); + var node = filter == null ? new ForEachIncludingUnassignedUniNode<>(forEachClass, tupleLifecycle, outputStoreSize) + : new ForEachExcludingUnassignedUniNode<>(forEachClass, filter, tupleLifecycle, outputStoreSize); buildHelper.addNode(node, this, null); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingNullVarsUniNode.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingUnassignedUniNode.java similarity index 88% rename from core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingNullVarsUniNode.java rename to core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingUnassignedUniNode.java index dde8dd6f4f..1daff7114c 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingNullVarsUniNode.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachExcludingUnassignedUniNode.java @@ -6,11 +6,11 @@ import ai.timefold.solver.constraint.streams.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.constraint.streams.bavet.common.tuple.UniTuple; -public final class ForEachExcludingNullVarsUniNode extends AbstractForEachUniNode { +public final class ForEachExcludingUnassignedUniNode extends AbstractForEachUniNode { private final Predicate filter; - public ForEachExcludingNullVarsUniNode(Class forEachClass, Predicate filter, + public ForEachExcludingUnassignedUniNode(Class forEachClass, Predicate filter, TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { super(forEachClass, nextNodesTupleLifecycle, outputStoreSize); this.filter = Objects.requireNonNull(filter); diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingNullVarsUniNode.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingUnassignedUniNode.java similarity index 73% rename from core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingNullVarsUniNode.java rename to core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingUnassignedUniNode.java index abac41c5bd..199a9e90c5 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingNullVarsUniNode.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/bavet/uni/ForEachIncludingUnassignedUniNode.java @@ -3,9 +3,9 @@ import ai.timefold.solver.constraint.streams.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.constraint.streams.bavet.common.tuple.UniTuple; -public final class ForEachIncludingNullVarsUniNode extends AbstractForEachUniNode { +public final class ForEachIncludingUnassignedUniNode extends AbstractForEachUniNode { - public ForEachIncludingNullVarsUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, + public ForEachIncludingUnassignedUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { super(forEachClass, nextNodesTupleLifecycle, outputStoreSize); } diff --git a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/common/RetrievalSemantics.java b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/common/RetrievalSemantics.java index 8413f1ae57..a5787ed3ed 100644 --- a/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/common/RetrievalSemantics.java +++ b/core/constraint-streams/src/main/java/ai/timefold/solver/constraint/streams/common/RetrievalSemantics.java @@ -21,9 +21,9 @@ public enum RetrievalSemantics { /** * Joins do not include entities with null planning variables, - * unless specifically requested by join(forEachIncludingNullVars(...)). + * unless specifically requested by join(forEachIncludingUnassigned(...)). * Conditional propagation does not include null planning variables, - * unless specifically requested using a *IncludingNullVars() method overload. + * unless specifically requested using a *IncludingUnassigned() method overload. * *

* Applies when the stream comes off of a {@link ConstraintFactory#forEach(Class)} family of methods. diff --git a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java index 6755e9fa88..664a90ff44 100644 --- a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java +++ b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java @@ -189,7 +189,7 @@ public void filteringJoinNullConflictDifferentNodes() { InnerScoreDirector scoreDirector = buildScoreDirector(TestdataSolution.buildSolutionDescriptor(), factory -> new Constraint[] { - factory.forEachIncludingNullVars(TestdataEntity.class) + factory.forEachIncludingUnassigned(TestdataEntity.class) .filter(a -> a.getValue() != null) .join(TestdataEntity.class, filtering((a, b) -> { @@ -246,7 +246,7 @@ public void filteringIfExistsNullConflictDifferentNodes() { InnerScoreDirector scoreDirector = buildScoreDirector(TestdataSolution.buildSolutionDescriptor(), factory -> new Constraint[] { - factory.forEachIncludingNullVars(TestdataEntity.class) + factory.forEachIncludingUnassigned(TestdataEntity.class) .filter(a -> a.getValue() != null) .ifExists(TestdataEntity.class, filtering((a, b) -> { @@ -300,7 +300,7 @@ public void filteringIfNotExistsNullConflictDifferentNodes() { InnerScoreDirector scoreDirector = buildScoreDirector(TestdataSolution.buildSolutionDescriptor(), factory -> new Constraint[] { - factory.forEachIncludingNullVars(TestdataEntity.class) + factory.forEachIncludingUnassigned(TestdataEntity.class) .filter(a -> a.getValue() != null) .ifNotExists(TestdataEntity.class, filtering((a, b) -> { @@ -359,7 +359,7 @@ public void mapPlanningEntityChanges() { InnerScoreDirector scoreDirector = buildScoreDirector(TestdataSolution.buildSolutionDescriptor(), factory -> new Constraint[] { - factory.forEachIncludingNullVars(TestdataEntity.class) + factory.forEachIncludingUnassigned(TestdataEntity.class) .map(Function.identity()) .filter(e -> e.getValue() != null) .penalize(SimpleScore.ONE) diff --git a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/common/inliner/AbstractScoreInlinerTest.java b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/common/inliner/AbstractScoreInlinerTest.java index 23a274fdf1..5285d3984f 100644 --- a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/common/inliner/AbstractScoreInlinerTest.java +++ b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/common/inliner/AbstractScoreInlinerTest.java @@ -60,7 +60,7 @@ public UniConstraintStream forEach(Class sourceClass) { } @Override - public UniConstraintStream forEachIncludingNullVars(Class sourceClass) { + public UniConstraintStream forEachIncludingUnassigned(Class sourceClass) { throw new UnsupportedOperationException(); } diff --git a/core/core-impl/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintFactory.java b/core/core-impl/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintFactory.java index 4253038b8c..3aad769a4d 100644 --- a/core/core-impl/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintFactory.java +++ b/core/core-impl/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintFactory.java @@ -9,7 +9,6 @@ import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; -import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream; @@ -42,72 +41,41 @@ public interface ConstraintFactory { * {@link UniConstraintStream#filter(Predicate) filtered} to only contain entities * for which each genuine {@link PlanningVariable} (of the sourceClass or a superclass thereof) has a non-null value. *

- * This does not apply to {@link PlanningListVariable#allowsUnassignedValues()}. - * Elements can be unassigned (not present in any list) and this method will still include them. - * This situation needs to be handled by adding a {@link InverseRelationShadowVariable} on the element, - * exposing it via a new method such as {@code getUnassigned()}, - * and then calling {@link UniConstraintStream#filter(Predicate)} with that method. - *

- * Example: - * - *

-     * {@code
-     *     @PlanningEntity
-     *     public class Vehicle {
-     *
-     *         @PlanningListVariable(allowsUnassignedValues = true)
-     *         List customerList = ...;
-     *
-     *         ...
-     *
-     *     }
-     *
-     *     @PlanningEntity
-     *     public class Customer {
-     *
-     *         @InverseRelationShadowVariable(sourceVariableName = "customerList")
-     *         private Vehicle entity;
-     *
-     *         public boolean isAssigned() {
-     *             return entity != null;
-     *         }
-     *
-     *         ...
-     *
-     *     }
-     *
-     *     public class VehicleRoutingConstraintProvider implements ConstraintProvider {
+     * If the sourceClass is a shadow entity (an entity without any genuine planning variables),
+     * and if there exists a genuine {@link PlanningEntity} with a {@link PlanningListVariable}
+     * which accepts instances of this shadow entity as values in that list,
+     * and if that list variable {@link PlanningListVariable#allowsUnassignedValues() allows unassigned values},
+     * then this stream will filter out all sourceClass instances
+     * which are not present in any instances of that list variable.
      *
-     *         ...
-     *
-     *         public Constraint everyAssignedCustomer(ConstraintFactory constraintFactory) {
-     *             return constraintFactory.forEach(Customer.class)
-     *                 .filter(Customer::isAssigned)
-     *                 ...
-     *         }
-     *
-     *         ...
-     *
-     *     }
-     * }
-     * 
- * - * * @param sourceClass never null * @param the type of the matched problem fact or {@link PlanningEntity planning entity} * @return never null */ UniConstraintStream forEach(Class sourceClass); + /** + * As defined by {@link #forEachIncludingUnassigned(Class)}. + * + * @deprecated Use {@link #forEachIncludingUnassigned(Class)} instead. + */ + @Deprecated(forRemoval = true, since = "1.7.0") + default UniConstraintStream forEachIncludingNullVars(Class sourceClass) { + return forEachIncludingUnassigned(sourceClass); + } + /** * As defined by {@link #forEach(Class)}, - * but without any filtering of null {@link PlanningEntity planning entity} variables. + * but without any filtering of unassigned {@link PlanningEntity planning entities} + * (for {@link PlanningVariable#allowsUnassigned()}) + * or shadow entities not assigned to any applicable list variable + * (for {@link PlanningListVariable#allowsUnassignedValues()}). * * @param sourceClass never null * @param the type of the matched problem fact or {@link PlanningEntity planning entity} * @return never null */ - UniConstraintStream forEachIncludingNullVars(Class sourceClass); + UniConstraintStream forEachIncludingUnassigned(Class sourceClass); /** * Create a new {@link BiConstraintStream} for every unique combination of A and another A with a higher {@link PlanningId}. @@ -233,7 +201,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ * Calls to the {@link #forEach(Class)} family of methods will now filter out planning entities with null variables, * so most constraints no longer need to do null checks, * but the constraint that penalizes unassigned entities (typically a medium constraint) - * must now use {@link #forEachIncludingNullVars(Class)} instead. + * must now use {@link #forEachIncludingUnassigned(Class)} instead. * Subsequent joins and conditional propagation calls will now also consistently filter out planning entities with null * variables. * @@ -260,7 +228,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ /** * This method is deprecated. - * Migrate uses of this method to {@link #forEachIncludingNullVars(Class)}, + * Migrate uses of this method to {@link #forEachIncludingUnassigned(Class)}, * but first understand that subsequent joins and conditional propagation calls * ({@link UniConstraintStream#ifExists} etc.) * will now also consistently filter out planning entities with null variables. @@ -270,7 +238,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ * As defined by {@link #from(Class)}, * but without any filtering of uninitialized {@link PlanningEntity planning entities}. * - * @deprecated Prefer {@link #forEachIncludingNullVars(Class)}. + * @deprecated Prefer {@link #forEachIncludingUnassigned(Class)}. * @param fromClass never null * @param the type of the matched problem fact or {@link PlanningEntity planning entity} * @return never null diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc index e3b9cd3632..71afea3a3a 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc @@ -221,12 +221,12 @@ or a xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntities and has no `null` genuine planning variables. To include instances with a `null` genuine planning variable, -replace the `forEach()` building block by `forEachIncludingNullVars()`: +replace the `forEach()` building block by `forEachIncludingUnassigned()`: [source,java,options="nowrap"] ---- private Constraint penalizeAllShifts(ConstraintFactory factory) { - return factory.forEachIncludingNullVars(Shift.class) + return factory.forEachIncludingUnassigned(Shift.class) .penalize(HardSoftScore.ONE_SOFT) .asConstraint("A shift"); } diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc index 363966692b..fdbd791719 100644 --- a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc +++ b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc @@ -334,7 +334,7 @@ To allow an initialized planning variable to be ``null``, set `allows_unassigned [NOTE] ==== Constraint Streams filter out planning entities with a `null` planning variable by default. -Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingNullVars()] to avoid such unwanted behaviour. +Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingUnassigned()] to avoid such unwanted behaviour. ==== Timefold Solver will automatically add the value `null` to the value range. diff --git a/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java index 7f94c7f685..8f354e9d0b 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java +++ b/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java @@ -138,7 +138,7 @@ Constraint roomConflict(ConstraintFactory factory) { } Constraint speakerUnavailableTimeslot(ConstraintFactory factory) { - return factory.forEachIncludingNullVars(Talk.class) + return factory.forEachIncludingUnassigned(Talk.class) .filter(talk -> talk.getTimeslot() != null) .join(Speaker.class, filtering((talk, speaker) -> talk.hasSpeaker(speaker) diff --git a/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java index 9705189f1a..3fc0a13969 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java +++ b/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java @@ -51,7 +51,7 @@ protected Constraint roomConflict(ConstraintFactory constraintFactory) { } protected Constraint avoidOvertime(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null) .ifNotExists(TimeGrain.class, equal(MeetingAssignment::getLastTimeGrainIndex, TimeGrain::getGrainIndex)) @@ -82,7 +82,7 @@ protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFact } protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getRequiredCapacity() > meetingAssignment.getRoomCapacity()) .penalizeConfigurable( meetingAssignment -> meetingAssignment.getRequiredCapacity() - meetingAssignment.getRoomCapacity()) @@ -90,7 +90,7 @@ protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) { } protected Constraint startAndEndOnSameDay(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null) .join(TimeGrain.class, equal(MeetingAssignment::getLastTimeGrainIndex, TimeGrain::getGrainIndex), @@ -108,11 +108,11 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co return constraintFactory.forEach(RequiredAttendance.class) .join(PreferredAttendance.class, equal(RequiredAttendance::getPerson, PreferredAttendance::getPerson)) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(assignment -> assignment.getStartingTimeGrain() != null), equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(), MeetingAssignment::getMeeting)) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(assignment -> assignment.getStartingTimeGrain() != null), equal((requiredAttendance, preferredAttendance, leftAssignment) -> preferredAttendance.getMeeting(), MeetingAssignment::getMeeting), @@ -131,11 +131,11 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFactory) { return constraintFactory.forEachUniquePair(PreferredAttendance.class, equal(PreferredAttendance::getPerson)) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(assignment -> assignment.getStartingTimeGrain() != null), equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(), MeetingAssignment::getMeeting)) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(assignment -> assignment.getStartingTimeGrain() != null), equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(), MeetingAssignment::getMeeting), @@ -156,16 +156,16 @@ protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFac // ************************************************************************ protected Constraint doMeetingsAsSoonAsPossible(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null) .penalizeConfigurable(MeetingAssignment::getLastTimeGrainIndex) .asConstraint("Do all meetings as soon as possible"); } protected Constraint oneBreakBetweenConsecutiveMeetings(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(assignment -> assignment.getStartingTimeGrain() != null), equal(MeetingAssignment::getLastTimeGrainIndex, (rightAssignment) -> rightAssignment.getStartingTimeGrain().getGrainIndex() - 1)) @@ -174,9 +174,9 @@ protected Constraint oneBreakBetweenConsecutiveMeetings(ConstraintFactory constr } protected Constraint overlappingMeetings(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null) - .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null), greaterThan((leftAssignment) -> leftAssignment.getMeeting().getId(), (rightAssignment) -> rightAssignment.getMeeting().getId()), @@ -188,7 +188,7 @@ protected Constraint overlappingMeetings(ConstraintFactory constraintFactory) { } protected Constraint assignLargerRoomsFirst(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class) + return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class) .filter(meetingAssignment -> meetingAssignment.getRoom() != null) .join(Room.class, lessThan(MeetingAssignment::getRoomCapacity, Room::getCapacity)) diff --git a/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java index 7be198b0da..d42db0d947 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java +++ b/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java @@ -59,7 +59,7 @@ public Constraint sameBedInSameNightConstraint(ConstraintFactory constraintFacto } public Constraint femaleInMaleRoomConstraint(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(BedDesignation.class) + return constraintFactory.forEachIncludingUnassigned(BedDesignation.class) .filter(bd -> bd.getPatientGender() == Gender.FEMALE && bd.getRoomGenderLimitation() == GenderLimitation.MALE_ONLY) .penalize(HardMediumSoftScore.ofHard(50), BedDesignation::getAdmissionPartNightCount) @@ -67,7 +67,7 @@ public Constraint femaleInMaleRoomConstraint(ConstraintFactory constraintFactory } public Constraint maleInFemaleRoomConstraint(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(BedDesignation.class) + return constraintFactory.forEachIncludingUnassigned(BedDesignation.class) .filter(bd -> bd.getPatientGender() == Gender.MALE && bd.getRoomGenderLimitation() == GenderLimitation.FEMALE_ONLY) .penalize(HardMediumSoftScore.ofHard(50), BedDesignation::getAdmissionPartNightCount) @@ -89,9 +89,9 @@ public Constraint differentGenderInSameGenderRoomInSameNightConstraint(Constrain } public Constraint departmentMinimumAgeConstraint(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(Department.class) + return constraintFactory.forEachIncludingUnassigned(Department.class) .filter(d -> d.getMinimumAge() != null) - .join(constraintFactory.forEachIncludingNullVars(BedDesignation.class), + .join(constraintFactory.forEachIncludingUnassigned(BedDesignation.class), equal(Function.identity(), BedDesignation::getDepartment), greaterThan(Department::getMinimumAge, BedDesignation::getPatientAge)) .penalize(HardMediumSoftScore.ofHard(100), @@ -100,9 +100,9 @@ public Constraint departmentMinimumAgeConstraint(ConstraintFactory constraintFac } public Constraint departmentMaximumAgeConstraint(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(Department.class) + return constraintFactory.forEachIncludingUnassigned(Department.class) .filter(d -> d.getMaximumAge() != null) - .join(constraintFactory.forEachIncludingNullVars(BedDesignation.class), + .join(constraintFactory.forEachIncludingUnassigned(BedDesignation.class), equal(Function.identity(), BedDesignation::getDepartment), lessThan(Department::getMaximumAge, BedDesignation::getPatientAge)) .penalize(HardMediumSoftScore.ofHard(100), @@ -124,7 +124,7 @@ public Constraint requiredPatientEquipmentConstraint(ConstraintFactory constrain //Medium public Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) { - return constraintFactory.forEachIncludingNullVars(BedDesignation.class) + return constraintFactory.forEachIncludingUnassigned(BedDesignation.class) .filter(bd -> bd.getBed() == null) .penalize(HardMediumSoftScore.ONE_MEDIUM, BedDesignation::getAdmissionPartNightCount) .asConstraint("assignEveryPatientToABed");