diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ShadowVariableUpdateHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ShadowVariableUpdateHelper.java index cf8ee184c2..661b407bdd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ShadowVariableUpdateHelper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ShadowVariableUpdateHelper.java @@ -25,6 +25,7 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultShadowVariableMetaModel; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @@ -338,8 +339,8 @@ private List> fetchBasicDescriptors(EntityDes private static class InternalScoreDirectorFactory> extends AbstractScoreDirectorFactory> { - public InternalScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { - super(solutionDescriptor); + public InternalScoreDirectorFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { + super(solutionDescriptor, environmentMode); } @Override @@ -387,7 +388,8 @@ public static final class Builder> AbstractScoreDirectorBuilder, InternalScoreDirector.Builder> { public Builder(SolutionDescriptor solutionDescriptor) { - super(new InternalScoreDirectorFactory<>(solutionDescriptor)); + // We use PHASE_ASSERT by default + super(new InternalScoreDirectorFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT)); withConstraintMatchPolicy(DISABLED); withLookUpEnabled(false); withExpectShadowVariablesInCorrectState(false); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/DefaultMoveRunner.java b/core/src/main/java/ai/timefold/solver/core/impl/move/DefaultMoveRunner.java index fb7928d24f..15ab0b24b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/DefaultMoveRunner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/DefaultMoveRunner.java @@ -2,6 +2,7 @@ import java.util.Objects; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningSolutionMetaModel; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; @@ -16,9 +17,10 @@ public final class DefaultMoveRunner implements MoveRunner private final AbstractScoreDirectorFactory scoreDirectorFactory; public DefaultMoveRunner(PlanningSolutionMetaModel solutionMetaModel) { + // We use PHASE_ASSERT by default this(new MoveRunnerScoreDirectorFactory<>( - ((DefaultPlanningSolutionMetaModel) Objects.requireNonNull(solutionMetaModel)) - .solutionDescriptor())); + ((DefaultPlanningSolutionMetaModel) Objects.requireNonNull(solutionMetaModel)).solutionDescriptor(), + EnvironmentMode.PHASE_ASSERT)); } private DefaultMoveRunner(AbstractScoreDirectorFactory scoreDirectorFactory) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRunnerScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRunnerScoreDirectorFactory.java index 222e38cd9a..3b5c72c3b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRunnerScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRunnerScoreDirectorFactory.java @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.move; import ai.timefold.solver.core.api.score.Score; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; @@ -11,8 +12,8 @@ final class MoveRunnerScoreDirectorFactory> extends AbstractScoreDirectorFactory> { - public MoveRunnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { - super(solutionDescriptor); + public MoveRunnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { + super(solutionDescriptor, environmentMode); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java index 4ca56f985e..c0f0923a14 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java @@ -26,6 +26,7 @@ import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.domain.variable.InnerVariableListener; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; +import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport; @@ -100,6 +101,8 @@ public abstract class AbstractScoreDirector moveRepository; protected AbstractScoreDirector(AbstractScoreDirectorBuilder builder) { @@ -125,6 +128,8 @@ protected AbstractScoreDirector(AbstractScoreDirectorBuilder variableDescripto } variableListenerSupport.afterVariableChanged(variableDescriptor, entity); neighborhoodsElementUpdateNotifier.accept(entity); + if (isStepAssertOrMore) { + assertValueRangeForBasicVariables(entity); + } } @Override @@ -554,6 +566,10 @@ public void afterListVariableChanged(ListVariableDescriptor variableD int toIndex) { variableListenerSupport.afterListVariableChanged(variableDescriptor, entity, fromIndex, toIndex); neighborhoodsElementUpdateNotifier.accept(entity); + if (isStepAssertOrMore) { + var valueList = variableDescriptor.getValue(entity).subList(fromIndex, toIndex); + assertValueRangeForListVariable(entity, valueList); + } } public void beforeEntityRemoved(EntityDescriptor entityDescriptor, Object entity) { @@ -816,6 +832,90 @@ that could cause the scoreDifference (%s).""".formatted(scoreDifference, beforeM } } + private void assertValueRangeForEntity(Object entity) { + assertValueRangeForBasicVariables(entity); + var listVariableDescriptor = getSolutionDescriptor().getListVariableDescriptor(); + if (listVariableDescriptor != null) { + if (!listVariableDescriptor.getEntityDescriptor().matchesEntity(entity)) { + return; + } + var valueList = listVariableDescriptor.getValue(entity); + assertValueRangeForListVariable(entity, valueList); + } + } + + private void assertValueRangeForBasicVariables(Object entity) { + var entityDescriptor = getSolutionDescriptor().findEntityDescriptor(entity.getClass()); + if (entityDescriptor == null) { + // It may be called for a shadow entity + return; + } + var basicVariableDescriptorList = entityDescriptor.getGenuineBasicVariableDescriptorList().stream() + .map(v -> (BasicVariableDescriptor) v).toList(); + assertValueRangeForBasicVariables(this, basicVariableDescriptorList, entity); + } + + private void assertValueRangeForListVariable(Object entity, List valueList) { + var entityDescriptor = getSolutionDescriptor().findEntityDescriptor(entity.getClass()); + if (entityDescriptor == null) { + // It may be called for a shadow entity + return; + } + var listVariableDescriptor = entityDescriptor.getGenuineListVariableDescriptor(); + if (listVariableDescriptor == null) { + // The entity has no genuine list variable + return; + } + assertValueRangeForListVariable(this, listVariableDescriptor, entity, valueList); + } + + private static void assertValueRangeForBasicVariables(InnerScoreDirector scoreDirector, + List> basicVariableDescriptorList, Object entity) { + if (basicVariableDescriptorList == null || basicVariableDescriptorList.isEmpty()) { + return; + } + for (var variableDescriptor : basicVariableDescriptorList) { + var value = variableDescriptor.getValue(entity); + if (value == null) { + continue; + } + var valueRange = scoreDirector.getValueRangeManager() + .getFromEntity(variableDescriptor.getValueRangeDescriptor(), entity); + if (!valueRange.contains(value)) { + if (variableDescriptor.isChained()) { + // We also check the entity list + var allEntities = + variableDescriptor.getEntityDescriptor().extractEntities(scoreDirector.getWorkingSolution()); + if (allEntities.contains(value)) { + continue; + } + } + throw new IllegalStateException( + "The value (%s) from the planning variable (%s) has been assigned to the entity (%s), but it is outside of the related value range %s." + .formatted(value, variableDescriptor.getVariableName(), entity, valueRange)); + } + } + } + + private static void assertValueRangeForListVariable(InnerScoreDirector scoreDirector, + ListVariableDescriptor variableDescriptor, Object entity, List valueList) { + if (valueList.isEmpty()) { + return; + } + var valueRange = scoreDirector.getValueRangeManager() + .getFromEntity(variableDescriptor.getValueRangeDescriptor(), entity); + for (var value : valueList) { + if (value == null) { + continue; + } + if (!valueRange.contains(value)) { + throw new IllegalStateException( + "The value (%s) from the planning variable (%s) has been assigned to the entity (%s), but it is outside of the related value range %s." + .formatted(value, variableDescriptor.getVariableName(), entity, valueRange)); + } + } + } + public SolutionTracker.SolutionCorruptionResult getSolutionCorruptionAfterUndo(Move move, InnerScore undoInnerScore) { var trackingWorkingSolution = solutionTracker != null; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java index f2b9bc7d15..e5a9a4e66b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java @@ -27,6 +27,7 @@ public abstract class AbstractScoreDirectorFactory solutionDescriptor; + protected final EnvironmentMode environmentMode; protected final ListVariableDescriptor listVariableDescriptor; protected InitializingScoreTrend initializingScoreTrend; @@ -36,8 +37,9 @@ public abstract class AbstractScoreDirectorFactory solutionDescriptor) { + public AbstractScoreDirectorFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { this.solutionDescriptor = solutionDescriptor; + this.environmentMode = environmentMode; this.listVariableDescriptor = solutionDescriptor.getListVariableDescriptor(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java index 657c7cabbe..26a1783617 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java @@ -68,9 +68,9 @@ DRL constraints requested via scoreDrlList (%s), but this is no longer supported // At this point, we are guaranteed to have at most one score director factory selected. if (config.getEasyScoreCalculatorClass() != null) { - return EasyScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, config); + return EasyScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, config, environmentMode); } else if (config.getIncrementalScoreCalculatorClass() != null) { - return IncrementalScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, config); + return IncrementalScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, config, environmentMode); } else if (config.getConstraintProviderClass() != null) { return BavetConstraintStreamScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, config, environmentMode); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java index ffd0d03fab..e95953a721 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java @@ -4,6 +4,7 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; @@ -22,7 +23,8 @@ public final class EasyScoreDirectorFactory> { public static > EasyScoreDirectorFactory - buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config) { + buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config, + EnvironmentMode environmentMode) { var easyScoreCalculatorClass = config.getEasyScoreCalculatorClass(); if (easyScoreCalculatorClass == null || !EasyScoreCalculator.class.isAssignableFrom(easyScoreCalculatorClass)) { throw new IllegalArgumentException( @@ -33,14 +35,14 @@ public final class EasyScoreDirectorFactory(solutionDescriptor, easyScoreCalculator); + return new EasyScoreDirectorFactory<>(solutionDescriptor, easyScoreCalculator, environmentMode); } private final EasyScoreCalculator easyScoreCalculator; public EasyScoreDirectorFactory(SolutionDescriptor solutionDescriptor, - EasyScoreCalculator easyScoreCalculator) { - super(solutionDescriptor); + EasyScoreCalculator easyScoreCalculator, EnvironmentMode environmentMode) { + super(solutionDescriptor, environmentMode); this.easyScoreCalculator = easyScoreCalculator; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java index 16b946376d..6793844bbd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java @@ -7,6 +7,7 @@ import ai.timefold.solver.core.api.score.calculator.ConstraintMatchAwareIncrementalScoreCalculator; import ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator; import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; @@ -24,7 +25,8 @@ public final class IncrementalScoreDirectorFactory> { public static > IncrementalScoreDirectorFactory - buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config) { + buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config, + EnvironmentMode environmentMode) { if (!IncrementalScoreCalculator.class.isAssignableFrom(config.getIncrementalScoreCalculatorClass())) { throw new IllegalArgumentException( "The incrementalScoreCalculatorClass (%s) does not implement %s." @@ -37,14 +39,15 @@ public final class IncrementalScoreDirectorFactory> incrementalScoreCalculatorSupplier; public IncrementalScoreDirectorFactory(SolutionDescriptor solutionDescriptor, - Supplier> incrementalScoreCalculatorSupplier) { - super(solutionDescriptor); + Supplier> incrementalScoreCalculatorSupplier, + EnvironmentMode environmentMode) { + super(solutionDescriptor, environmentMode); this.incrementalScoreCalculatorSupplier = incrementalScoreCalculatorSupplier; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java index 10de34dcd9..df7939d398 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java @@ -66,7 +66,7 @@ public BavetConstraintStreamScoreDirectorFactory(SolutionDescriptor s public BavetConstraintStreamScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ConstraintProvider constraintProvider, EnvironmentMode environmentMode, boolean profilingEnabled) { - super(solutionDescriptor); + super(solutionDescriptor, environmentMode); var constraintFactory = new BavetConstraintFactory<>(solutionDescriptor, environmentMode); constraintMetaModel = DefaultConstraintMetaModel.of(constraintFactory.buildConstraints(constraintProvider)); constraintSessionFactory = diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintStreamScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintStreamScoreDirectorFactory.java index 7f3bceb99b..6429a7a805 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintStreamScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintStreamScoreDirectorFactory.java @@ -3,6 +3,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; @@ -18,8 +19,9 @@ public abstract class AbstractConstraintStreamScoreDirectorFactory, Factory_ extends AbstractConstraintStreamScoreDirectorFactory> extends AbstractScoreDirectorFactory { - protected AbstractConstraintStreamScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { - super(solutionDescriptor); + protected AbstractConstraintStreamScoreDirectorFactory(SolutionDescriptor solutionDescriptor, + EnvironmentMode environmentMode) { + super(solutionDescriptor, environmentMode); } /** diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/list/ElementDestinationSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/list/ElementDestinationSelectorTest.java index 6f7be8ae3e..33206e0e5d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/list/ElementDestinationSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/list/ElementDestinationSelectorTest.java @@ -313,7 +313,7 @@ void randomPartiallyPinnedAndUnassigned() { var v3 = new TestdataPinnedUnassignedValuesListValue("3"); var v4 = new TestdataPinnedUnassignedValuesListValue("4"); var v5 = new TestdataPinnedUnassignedValuesListValue("5"); - var v6 = new TestdataPinnedUnassignedValuesListValue("5"); + var v6 = new TestdataPinnedUnassignedValuesListValue("6"); var unassignedValue = new TestdataPinnedUnassignedValuesListValue("7"); var a = new TestdataPinnedUnassignedValuesListEntity("A", v1, v2); var b = new TestdataPinnedUnassignedValuesListEntity("B"); @@ -325,7 +325,7 @@ void randomPartiallyPinnedAndUnassigned() { var solution = new TestdataPinnedUnassignedValuesListSolution(); solution.setEntityList(List.of(a, b, c, d)); - solution.setValueList(List.of(v1, v2, v3, v3, v4, v5, unassignedValue)); + solution.setValueList(List.of(v1, v2, v3, v3, v4, v5, v6, unassignedValue)); SolutionManager.updateShadowVariables(solution); var random = new TestRandom( diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java index a0bc22e86e..2eca9b7e3a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java @@ -10,6 +10,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -56,7 +57,7 @@ void doMove() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(), - solution -> SimpleScore.ZERO); + solution -> SimpleScore.ZERO, EnvironmentMode.PHASE_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java index 13f1b4caa5..59a04955ad 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java @@ -12,6 +12,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; @@ -80,7 +81,7 @@ void doMove() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(), - solution -> SimpleScore.ZERO); + solution -> SimpleScore.ZERO, EnvironmentMode.PHASE_ASSERT); ScoreDirector scoreDirector = scoreDirectorFactory.buildScoreDirector(); var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java index fe21ba2ba0..54d9ed4049 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java @@ -11,6 +11,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; @@ -129,7 +130,7 @@ void doMove() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(), - solution -> SimpleScore.ZERO); + solution -> SimpleScore.ZERO, EnvironmentMode.PHASE_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); var variableDescriptorList = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor().getGenuineVariableDescriptorList(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java index a20767d443..3238b3e8c6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java @@ -12,6 +12,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; @@ -88,7 +89,7 @@ void doMove() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(), - solution -> SimpleScore.ZERO); + solution -> SimpleScore.ZERO, EnvironmentMode.PHASE_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); var entityDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java index 0de9781ee6..c2e831d5e9 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java @@ -817,7 +817,8 @@ void testSolverScopeNestedPhase() { void variableListenersAreTriggeredWhenSolutionIsConsistent() { var solutionDescriptor = TestdataShadowedFullSolution.buildSolutionDescriptor(); var scoreCalculator = new TestdataShadowedFullEasyScoreCalculator(); - var easyScoreDirectorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator); + var easyScoreDirectorFactory = + new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator, EnvironmentMode.PHASE_ASSERT); var innerScoreDirector = easyScoreDirectorFactory.buildScoreDirector(); var moveDirector = new MoveDirector<>(innerScoreDirector); @@ -864,7 +865,8 @@ void variableListenersAreTriggeredWhenSolutionIsConsistent() { void undoCascadingUpdateShadowVariable() { var solutionDescriptor = TestdataSingleCascadingSolution.buildSolutionDescriptor(); var scoreCalculator = new TestdataSingleCascadingEasyScoreCalculator(); - var scoreDirectorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator); + var scoreDirectorFactory = + new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator, EnvironmentMode.PHASE_ASSERT); var innerScoreDirector = scoreDirectorFactory.buildScoreDirector(); var moveDirector = new MoveDirector<>(innerScoreDirector); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsTest.java index 54929dfd1c..a7de353552 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsTest.java @@ -86,7 +86,8 @@ void changeMoveBasedLocalSearch() { solution.getEntityList().forEach(e -> e.setValue(secondValue)); var scoreDirector = - new EasyScoreDirectorFactory<>(solutionDescriptor, new TestingEasyScoreCalculator()).buildScoreDirector(); + new EasyScoreDirectorFactory<>(solutionDescriptor, new TestingEasyScoreCalculator(), + EnvironmentMode.PHASE_ASSERT).buildScoreDirector(); scoreDirector.setWorkingSolution(solution); var score = scoreDirector.calculateScore(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java index 4ef349878e..be135916e4 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java @@ -9,6 +9,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.InnerScore; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -22,7 +23,7 @@ void buildScoreDirector() { EasyScoreCalculator scoreCalculator = mock(EasyScoreCalculator.class); when(scoreCalculator.calculateScore(any(TestdataSolution.class))) .thenAnswer(invocation -> SimpleScore.of(-10)); - var directorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator); + var directorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, scoreCalculator, EnvironmentMode.PHASE_ASSERT); try (var director = directorFactory.buildScoreDirector()) { var solution = new TestdataSolution(); solution.setValueList(Collections.emptyList()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java index 9d9d2966d3..e0622dc70f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java @@ -6,6 +6,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.config.score.trend.InitializingScoreTrendLevel; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.score.director.InnerScore; import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -19,7 +20,7 @@ class EasyScoreDirectorTest { @Test void shadowVariableCorruption() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataCorruptedShadowedSolution.buildSolutionDescriptor(), - (solution_) -> SimpleScore.of(0)); + (solution_) -> SimpleScore.of(0), EnvironmentMode.PHASE_ASSERT); scoreDirectorFactory .setInitializingScoreTrend(InitializingScoreTrend.buildUniformTrend(InitializingScoreTrendLevel.ONLY_DOWN, 1)); try (var scoreDirector = scoreDirectorFactory.buildScoreDirector()) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/AbstractBiConstraintStreamTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/AbstractBiConstraintStreamTest.java index 288dd56881..b543846c7a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/AbstractBiConstraintStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/AbstractBiConstraintStreamTest.java @@ -1295,7 +1295,7 @@ public void groupBy_0Mapping1Collector() { InnerScoreDirector scoreDirector = buildScoreDirector(factory -> factory.forEachUniquePair(TestdataLavishEntity.class) .groupBy(countBi()) - .penalize(SimpleScore.ONE, (count) -> count) + .penalize(SimpleScore.ONE, count -> count) .asConstraint(TEST_CONSTRAINT_NAME)); // From scratch @@ -2076,6 +2076,8 @@ public void concatUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2120,6 +2122,8 @@ public void concatAndDistinctUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2165,6 +2169,8 @@ public void concatBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2211,6 +2217,8 @@ public void concatBiWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2257,6 +2265,8 @@ public void concatAndDistinctBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2304,6 +2314,8 @@ public void concatAndDistinctBiWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2349,6 +2361,8 @@ public void concatTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2397,6 +2411,8 @@ public void concatAndDistinctTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2446,6 +2462,8 @@ public void concatQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2496,6 +2514,8 @@ public void concatAndDistinctQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2547,6 +2567,8 @@ public void concatAfterGroupBy() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2602,6 +2624,7 @@ public void complement() { var solution = TestdataLavishSolution.generateSolution(2, 5, 1, 1); var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); + solution.getValueList().add(value2); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/AbstractQuadConstraintStreamTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/AbstractQuadConstraintStreamTest.java index 9525a0635f..1ecb3d8d0b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/AbstractQuadConstraintStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/AbstractQuadConstraintStreamTest.java @@ -1278,7 +1278,7 @@ public void flattenLastWithDuplicates() { buildScoreDirector(factory -> factory.forEachUniquePair(TestdataLavishEntity.class) .join(TestdataLavishEntity.class, filtering((a, b, c) -> a != c && b != c)) .join(TestdataLavishEntity.class, filtering((a, b, c, d) -> a != d && b != d)) - .flattenLast((d) -> asList(group1, group1, group2)) + .flattenLast(d -> asList(group1, group1, group2)) .penalize(SimpleScore.ONE) .asConstraint(TEST_CONSTRAINT_NAME)); @@ -1316,7 +1316,7 @@ public void flattenLastWithoutDuplicates() { buildScoreDirector(factory -> factory.forEachUniquePair(TestdataLavishEntity.class) .join(TestdataLavishEntity.class, filtering((a, b, c) -> a != c && b != c)) .join(TestdataLavishEntity.class, filtering((a, b, c, d) -> a != d && b != d)) - .flattenLast((d) -> asList(group1, group2)) + .flattenLast(d -> asList(group1, group2)) .penalize(SimpleScore.ONE) .asConstraint(TEST_CONSTRAINT_NAME)); @@ -1351,7 +1351,7 @@ public void flattenLastAndDistinctWithDuplicates() { buildScoreDirector(factory -> factory.forEachUniquePair(TestdataLavishEntity.class) .join(TestdataLavishEntity.class, filtering((a, b, c) -> a != c && b != c)) .join(TestdataLavishEntity.class, filtering((a, b, c, d) -> a != d && b != d)) - .flattenLast((d) -> asList(group1, group1, group2)) + .flattenLast(d -> asList(group1, group1, group2)) .distinct() .penalize(SimpleScore.ONE) .asConstraint(TEST_CONSTRAINT_NAME)); @@ -1387,7 +1387,7 @@ public void flattenLastAndDistinctWithoutDuplicates() { buildScoreDirector(factory -> factory.forEachUniquePair(TestdataLavishEntity.class) .join(TestdataLavishEntity.class, filtering((a, b, c) -> a != c && b != c)) .join(TestdataLavishEntity.class, filtering((a, b, c, d) -> a != d && b != d)) - .flattenLast((d) -> asList(group1, group2)) + .flattenLast(d -> asList(group1, group2)) .distinct() .penalize(SimpleScore.ONE) .asConstraint(TEST_CONSTRAINT_NAME)); @@ -1416,6 +1416,8 @@ public void concatUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1464,6 +1466,8 @@ public void concatAndDistinctUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1513,6 +1517,8 @@ public void concatBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1563,6 +1569,8 @@ public void concatAndDistinctBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1614,6 +1622,8 @@ public void concatTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1666,6 +1676,8 @@ public void concatAndDistinctTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1719,6 +1731,8 @@ public void concatQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1773,6 +1787,8 @@ public void concatQuadWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1827,6 +1843,8 @@ public void concatAndDistinctQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1882,6 +1900,8 @@ public void concatAndDistinctQuadWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1935,6 +1955,8 @@ public void concatAfterGroupBy() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1997,6 +2019,7 @@ public void complement() { var solution = TestdataLavishSolution.generateSolution(2, 5, 1, 1); var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); + solution.getValueList().add(value2); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/AbstractTriConstraintStreamTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/AbstractTriConstraintStreamTest.java index 50d6c3070e..eece9f2814 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/AbstractTriConstraintStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/AbstractTriConstraintStreamTest.java @@ -1771,6 +1771,8 @@ public void concatUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1817,6 +1819,8 @@ public void concatAndDistinctUniWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1864,6 +1868,8 @@ public void concatBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1912,6 +1918,8 @@ public void concatAndDistinctBiWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -1961,6 +1969,8 @@ public void concatTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2011,6 +2021,8 @@ public void concatTriWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2061,6 +2073,8 @@ public void concatAndDistinctTriWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2112,6 +2126,8 @@ public void concatAndDistinctTriWithValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2161,6 +2177,8 @@ public void concatQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2213,6 +2231,8 @@ public void concatAndDistinctQuadWithoutValueDuplicates() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2266,6 +2286,8 @@ public void concatAfterGroupBy() { TestdataLavishValue value1 = solution.getFirstValue(); TestdataLavishValue value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); TestdataLavishValue value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); TestdataLavishEntity entity1 = solution.getFirstEntity(); TestdataLavishEntity entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2325,6 +2347,7 @@ public void complement() { var solution = TestdataLavishSolution.generateSolution(2, 5, 1, 1); var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); + solution.getValueList().add(value2); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/AbstractUniConstraintStreamTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/AbstractUniConstraintStreamTest.java index 55ee9f2dfb..b4beaef8d5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/AbstractUniConstraintStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/AbstractUniConstraintStreamTest.java @@ -240,6 +240,7 @@ public void join_1Equal() { var solution = TestdataLavishSolution.generateSolution(2, 5, 1, 1); var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); + solution.getValueList().add(value2); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2592,6 +2593,8 @@ public void concatUniWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2634,6 +2637,8 @@ public void concatUniWithValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2676,6 +2681,8 @@ public void concatAndDistinctUniWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2719,6 +2726,8 @@ public void concatAndDistinctUniWithValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2760,6 +2769,8 @@ public void concatBiWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2804,6 +2815,8 @@ public void concatAndDistinctBiWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2849,6 +2862,8 @@ public void concatTriWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2895,6 +2910,8 @@ public void concatAndDistinctTriWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2942,6 +2959,8 @@ public void concatQuadWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -2990,6 +3009,8 @@ public void concatAndDistinctQuadWithoutValueDuplicates() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -3039,6 +3060,8 @@ public void concatAfterGroupBy() { var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); var value3 = new TestdataLavishValue("MyValue 3", solution.getFirstValueGroup()); + solution.getValueList().add(value2); + solution.getValueList().add(value3); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); @@ -3089,6 +3112,7 @@ public void complement() { var solution = TestdataLavishSolution.generateSolution(2, 5, 1, 1); var value1 = solution.getFirstValue(); var value2 = new TestdataLavishValue("MyValue 2", solution.getFirstValueGroup()); + solution.getValueList().add(value2); var entity1 = solution.getFirstEntity(); var entity2 = new TestdataLavishEntity("MyEntity 2", solution.getFirstEntityGroup(), value2); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index 37563ee918..311fb9e7d0 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; @@ -70,6 +71,8 @@ import ai.timefold.solver.core.config.solver.PreviewFeature; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; +import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; import ai.timefold.solver.core.impl.score.DummySimpleScoreEasyScoreCalculator; import ai.timefold.solver.core.impl.score.constraint.DefaultConstraintMatchTotal; import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; @@ -101,12 +104,17 @@ import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEntity; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListSolution; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListValue; +import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingValue; import ai.timefold.solver.core.testdomain.list.valuerange.unassignedvar.TestdataListUnassignedEntityProvidingEntity; import ai.timefold.solver.core.testdomain.list.valuerange.unassignedvar.TestdataListUnassignedEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.unassignedvar.TestdataListUnassignedEntityProvidingSolution; import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntityFirstEntity; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntityFirstValue; import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySecondEntity; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySecondValue; import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution; import ai.timefold.solver.core.testdomain.mixed.singleentity.MixedCustomMoveIteratorFactory; import ai.timefold.solver.core.testdomain.mixed.singleentity.MixedCustomPhaseCommand; @@ -136,6 +144,10 @@ import ai.timefold.solver.core.testdomain.sort.comparator.OneValuePerEntityComparatorEasyScoreCalculator; import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableEntity; import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.entityproviding.multivar.TestdataAllowsUnassignedMultiVarEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.entityproviding.multivar.TestdataAllowsUnassignedMultiVarEntityProvidingSolution; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingSolution; @@ -1960,6 +1972,151 @@ void failRuinRecreateWithMultiEntityMultiVar() { .hasMessageContaining("it cannot be deduced automatically"); } + @Test + void failBasicVariableInvalidValueRange() { + // Solver config + var solverConfig = + PlannerTestUtils.buildSolverConfig(TestdataEntityProvidingSolution.class, TestdataEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = new TestdataEntityProvidingSolution(); + var v1 = new TestdataValue("1"); + var v2 = new TestdataValue("2"); + var v3 = new TestdataValue("3"); + // The entity has an assigned value v3 that is not included in the entity value ranges + var e1 = new TestdataEntityProvidingEntity("e1", List.of(v1, v2), v3); + var e2 = new TestdataEntityProvidingEntity("e2", List.of(v1, v2), v1); + problem.setEntityList(new ArrayList<>(Arrays.asList(e1, e2))); + + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (3) from the planning variable (value) has been assigned to the entity (e1), but it is outside of the related value range [1-2]"); + } + + @Test + void failMultipleBasicVariableInvalidValueRange() { + // Solver config + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataAllowsUnassignedMultiVarEntityProvidingSolution.class, + TestdataAllowsUnassignedMultiVarEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = new TestdataAllowsUnassignedMultiVarEntityProvidingSolution(); + var v1 = new TestdataValue("1"); + var v2 = new TestdataValue("2"); + var v3 = new TestdataValue("3"); + // The entity has been assigned a value v3 for the second value range, + // which is not included in the entity's value ranges + var e1 = new TestdataAllowsUnassignedMultiVarEntityProvidingEntity("e1", List.of(v1, v2, v3), v3, List.of(v1, v2), v3, + v3); + var e2 = new TestdataAllowsUnassignedMultiVarEntityProvidingEntity("e2", List.of(v1, v2, v3), v1, List.of(v1, v2, v3), + v1, v1); + problem.setEntityList(new ArrayList<>(Arrays.asList(e1, e2))); + + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (3) from the planning variable (secondValue) has been assigned to the entity (e1), but it is outside of the related value range [null]∪[1-2]"); + } + + @Test + void failListVariableInvalidValueRange() { + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataListEntityProvidingSolution.class, TestdataListEntityProvidingEntity.class, + TestdataListEntityProvidingValue.class) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = new TestdataListEntityProvidingSolution(); + var v1 = new TestdataListEntityProvidingValue("1"); + var v2 = new TestdataListEntityProvidingValue("2"); + var v3 = new TestdataListEntityProvidingValue("3"); + // The entity has an assigned value v3 that is not included in the entity value ranges + var e1 = new TestdataListEntityProvidingEntity("e1", List.of(v1, v2), List.of(v3)); + var e2 = new TestdataListEntityProvidingEntity("e2", List.of(v1, v2), List.of(v1)); + problem.setEntityList(new ArrayList<>(Arrays.asList(e1, e2))); + + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (3) from the planning variable (valueList) has been assigned to the entity (e1), but it is outside of the related value range [1-2]"); + } + + @Test + void failMixedModelInvalidValueRange() { + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedMultiEntitySolution.class, TestdataMixedMultiEntityFirstEntity.class, + TestdataMixedMultiEntitySecondEntity.class) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = new TestdataMixedMultiEntitySolution(); + var v1a = new TestdataMixedMultiEntityFirstValue("1"); + var v2a = new TestdataMixedMultiEntityFirstValue("2"); + var v3a = new TestdataMixedMultiEntityFirstValue("3"); + var v1b = new TestdataMixedMultiEntitySecondValue("1", 1); + var v2b = new TestdataMixedMultiEntitySecondValue("2", 1); + var v3b = new TestdataMixedMultiEntitySecondValue("3", 1); + + // 1 - Invalid basic variable + var e1a = new TestdataMixedMultiEntityFirstEntity("e1", 1); + var e1b = new TestdataMixedMultiEntitySecondEntity("e1"); + e1b.setBasicValue(v1b); + var e2b = new TestdataMixedMultiEntitySecondEntity("e2"); + // Invalid assigned value + problem.setEntityList(List.of(e1a)); + problem.setOtherEntityList(List.of(e1b, e2b)); + problem.setValueList(List.of(v1a, v2a, v3a)); + problem.setOtherValueList(List.of(v2b, v3b)); + + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (1) from the planning variable (basicValue) has been assigned to the entity (e1), but it is outside of the related value range [2-3]"); + e1b.setBasicValue(null); + + // 2 - Invalid list variable + // Invalid assigned value + e1a.getValueList().add(v1a); + problem.setEntityList(List.of(e1a)); + problem.setOtherEntityList(List.of(e1b, e2b)); + problem.setValueList(List.of(v2a, v3a)); + problem.setOtherValueList(List.of(v2b, v3b)); + + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (1) from the planning variable (valueList) has been assigned to the entity (e1), but it is outside of the related value range [2-3]"); + } + + @Test + void failLocalSearchValueRangeAssertion() { + var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataListSolution.class, TestdataListEntity.class, + TestdataListValue.class); + solverConfig.setEnvironmentMode(EnvironmentMode.FULL_ASSERT); + var localSearchPhaseConfig = new LocalSearchPhaseConfig(); + localSearchPhaseConfig.setMoveSelectorConfig( + new MoveIteratorFactoryConfig().withMoveIteratorFactoryClass(InvalidMoveListFactory.class)); + solverConfig.setPhaseConfigList(List.of(new ConstructionHeuristicPhaseConfig(), localSearchPhaseConfig)); + + var problem = TestdataListSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (bad value) from the planning variable (valueList) has been assigned to the entity (Generated Entity 0), but it is outside of the related value range [Generated Value 0-Generated Value 1]"); + } + + @Test + void failCustomPhaseValueRangeAssertion() { + var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataListSolution.class, TestdataListEntity.class, + TestdataListValue.class); + solverConfig.setEnvironmentMode(EnvironmentMode.STEP_ASSERT); + var customPhaseConfig = new CustomPhaseConfig() + .withCustomPhaseCommands(new InvalidCustomPhaseCommand()); + solverConfig.setPhaseConfigList(List.of(new ConstructionHeuristicPhaseConfig(), customPhaseConfig)); + + var problem = TestdataListSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .hasMessageContaining( + "The value (bad value) from the planning variable (valueList) has been assigned to the entity (Generated Entity 0), but it is outside of the related value range [Generated Value 0-Generated Value 1]"); + } + public static final class MinimizeUnusedEntitiesEasyScoreCalculator implements EasyScoreCalculator { @@ -2209,4 +2366,51 @@ public static final class TestingMixedEasyScoreCalculator } + public static final class InvalidCustomPhaseCommand implements PhaseCommand { + + @Override + public void changeWorkingSolution(ScoreDirector scoreDirector, + BooleanSupplier isPhaseTerminated) { + var entity = scoreDirector.getWorkingSolution().getEntityList().get(0); + scoreDirector.beforeListVariableChanged(entity, "valueList", 0, 0); + entity.getValueList().add(new TestdataListValue("bad value")); + scoreDirector.afterListVariableChanged(entity, "valueList", 0, entity.getValueList().size()); + } + } + + public static final class InvalidMoveListFactory implements MoveIteratorFactory { + @Override + public long getSize(ScoreDirector scoreDirector) { + return 1; + } + + @Override + public Iterator + createOriginalMoveIterator(ScoreDirector scoreDirector) { + return List.of(new InvalidMove()).iterator(); + } + + @Override + public Iterator createRandomMoveIterator( + ScoreDirector scoreDirector, + Random workingRandom) { + return createOriginalMoveIterator(scoreDirector); + } + } + + public static final class InvalidMove extends AbstractMove { + + @Override + protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { + var entity = scoreDirector.getWorkingSolution().getEntityList().get(0); + scoreDirector.beforeListVariableChanged(entity, "valueList", 0, 0); + entity.getValueList().add(new TestdataListValue("bad value")); + scoreDirector.afterListVariableChanged(entity, "valueList", 0, entity.getValueList().size()); + } + + @Override + public boolean isMoveDoable(ScoreDirector scoreDirector) { + return true; + } + } } diff --git a/core/src/test/java/ai/timefold/solver/core/preview/api/neighborhood/stream/enumerating/UniEnumeratingStreamTest.java b/core/src/test/java/ai/timefold/solver/core/preview/api/neighborhood/stream/enumerating/UniEnumeratingStreamTest.java index d5279ea48f..f492d134b1 100644 --- a/core/src/test/java/ai/timefold/solver/core/preview/api/neighborhood/stream/enumerating/UniEnumeratingStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/preview/api/neighborhood/stream/enumerating/UniEnumeratingStreamTest.java @@ -165,7 +165,8 @@ private static DatasetSession createSession( EnumeratingStreamFactory enumeratingStreamFactory, Solution_ solution) { var scoreDirector = - new EasyScoreDirectorFactory<>(enumeratingStreamFactory.getSolutionDescriptor(), s -> SimpleScore.ZERO) + new EasyScoreDirectorFactory<>(enumeratingStreamFactory.getSolutionDescriptor(), s -> SimpleScore.ZERO, + EnvironmentMode.PHASE_ASSERT) .buildScoreDirector(); scoreDirector.setWorkingSolution(solution); var sessionContext = new SessionContext<>(scoreDirector); @@ -529,4 +530,4 @@ void forEachListVariableExcludingPinnedValuesIncludingNull() { .containsExactly(null, value2, value3, value4); } -} \ No newline at end of file +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/clone/customcloner/TestdataCorrectlyClonedSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/customcloner/TestdataCorrectlyClonedSolution.java index 548c5d7c59..ca84660a6d 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/clone/customcloner/TestdataCorrectlyClonedSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/clone/customcloner/TestdataCorrectlyClonedSolution.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.testdomain.clone.customcloner; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import ai.timefold.solver.core.api.domain.solution.PlanningEntityProperty; @@ -24,11 +24,13 @@ public class TestdataCorrectlyClonedSolution implements SolutionCloner staticList = List.of(new TestdataValue("1"), new TestdataValue("2")); + @ValueRangeProvider(id = "valueRange") @ProblemFactCollectionProperty public List valueRange() { // two values needed to allow for at least one doable move, otherwise the second step ends in an infinite loop - return Arrays.asList(new TestdataValue("1"), new TestdataValue("2")); + return new ArrayList<>(staticList); } @Override diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/TestdataEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/TestdataEntityProvidingSolution.java index f4dda05a60..93f2234e9e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/TestdataEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/TestdataEntityProvidingSolution.java @@ -71,7 +71,11 @@ private static TestdataEntityProvidingSolution generateSolution(int valueListSiz } } var entity = new TestdataEntityProvidingEntity("Generated Entity " + i, valueRange); - entity.setValue(initialized ? valueList.get(i % valueListSize) : null); + var value = initialized ? valueList.get(i % valueListSize) : null; + entity.setValue(value); + if (value != null && !entity.getValueRange().contains(value)) { + entity.getValueRange().add(value); + } entityList.add(entity); } solution.setEntityList(entityList); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/unassignedvar/TestdataAllowsUnassignedEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/unassignedvar/TestdataAllowsUnassignedEntityProvidingSolution.java index c009c306c7..190b9cbbf8 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/unassignedvar/TestdataAllowsUnassignedEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/entityproviding/unassignedvar/TestdataAllowsUnassignedEntityProvidingSolution.java @@ -72,7 +72,11 @@ private static TestdataAllowsUnassignedEntityProvidingSolution generateSolution( } } var entity = new TestdataAllowsUnassignedEntityProvidingEntity("Generated Entity " + i, valueRange); - entity.setValue(initialized ? valueList.get(i % valueListSize) : null); + var value = initialized ? valueList.get(i % valueListSize) : null; + entity.setValue(value); + if (value != null && !entity.getValueRange().contains(value)) { + entity.getValueRange().add(value); + } entityList.add(entity); } solution.setEntityList(entityList); diff --git a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java index 560f99f2fd..0b54228fe0 100644 --- a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java +++ b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java @@ -25,6 +25,7 @@ import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.score.trend.InitializingScoreTrendLevel; +import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @@ -136,7 +137,8 @@ public static TestdataSolution generateTestdataSolution(String code, int entityA public static InnerScoreDirector mockScoreDirector(SolutionDescriptor solutionDescriptor, boolean useSolution) { - var scoreDirectorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, solution_ -> SimpleScore.of(0)); + var scoreDirectorFactory = new EasyScoreDirectorFactory<>(solutionDescriptor, solution_ -> SimpleScore.of(0), + EnvironmentMode.PHASE_ASSERT); scoreDirectorFactory .setInitializingScoreTrend(InitializingScoreTrend.buildUniformTrend(InitializingScoreTrendLevel.ONLY_DOWN, 1)); if (useSolution) {