From 3bb74e4084d79a4b78b19baa071ce3041a9804a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sun, 15 Feb 2026 08:12:45 +0100 Subject: [PATCH] refactor: unify move interfaces --- .../api/domain/common/ComparatorFactory.java | 2 +- .../core/api/domain/common/PlanningId.java | 2 +- .../score/analysis/ConstraintAnalysis.java | 5 +- .../core/api/solver/change/ProblemChange.java | 2 +- .../core/config/solver/EnvironmentMode.java | 2 +- .../bavet/common/AbstractFlattenNode.java | 4 +- .../common/RecordAndReplayPropagator.java | 2 +- .../decider/ConstructionHeuristicDecider.java | 7 +- .../placer/Placement.java | 6 - .../placer/PooledEntityPlacer.java | 4 +- .../placer/QueuedEntityPlacer.java | 4 +- .../placer/QueuedValuePlacer.java | 2 +- .../entity/descriptor/EntityDescriptor.java | 4 +- .../cloner/FieldAccessingSolutionCloner.java | 15 +- .../domain/valuerange/ValueRangeCache.java | 6 +- .../domain/variable/ListVariableState.java | 3 +- .../DefaultTopologicalOrderGraph.java | 5 +- .../impl/heuristic/move/AbstractMove.java | 102 -- .../move/AbstractSelectorBasedMove.java | 101 ++ .../move/AbstractSimplifiedMove.java | 36 - .../impl/heuristic/move/AbstractUndoMove.java | 64 - .../solver/core/impl/heuristic/move/Move.java | 204 --- .../impl/heuristic/move/MoveAdapters.java | 37 +- .../heuristic/move/NewIteratorAdapter.java | 21 - .../impl/heuristic/move/NewMoveAdapter.java | 81 -- .../impl/heuristic/move/NoChangeMove.java | 56 - ...e.java => SelectorBasedCompositeMove.java} | 75 +- .../move/SelectorBasedNoChangeMove.java | 58 + .../impl/heuristic/selector/Selector.java | 2 +- .../common/decorator/SelectionFilter.java | 2 +- .../SelectionProbabilityWeightFactory.java | 2 +- .../common/decorator/SelectionSorter.java | 2 +- .../SelectionSorterWeightFactory.java | 2 +- .../AbstractOriginalChangeIterator.java | 2 +- .../AbstractOriginalSwapIterator.java | 2 +- .../AbstractRandomChangeIterator.java | 2 +- .../iterator/AbstractRandomSwapIterator.java | 2 +- .../iterator/CachedListRandomIterator.java | 2 +- .../iterator/UpcomingSelectionIterator.java | 2 +- .../impl/heuristic/selector/list/SubList.java | 6 +- .../move/AbstractMoveSelectorFactory.java | 2 +- .../move/DoableMoveSelectionFilter.java | 8 +- .../heuristic/selector/move/MoveSelector.java | 2 +- .../BiasedRandomUnionMoveIterator.java | 2 +- .../CartesianProductMoveSelector.java | 18 +- .../UniformRandomUnionMoveIterator.java | 2 +- .../move/composite/UnionMoveSelector.java | 2 +- .../AbstractCachingMoveSelector.java | 2 +- .../move/decorator/CachingMoveSelector.java | 2 +- .../move/decorator/FilteringMoveSelector.java | 2 +- .../decorator/ProbabilityMoveSelector.java | 2 +- .../SelectedCountLimitMoveSelector.java | 2 +- .../move/decorator/ShufflingMoveSelector.java | 2 +- .../move/decorator/SortingMoveSelector.java | 2 +- .../move/factory/MoveIteratorFactory.java | 2 +- ...veIteratorFactoryToMoveSelectorBridge.java | 2 +- .../move/factory/MoveListFactory.java | 2 +- .../MoveListFactoryToMoveSelectorBridge.java | 2 +- .../move/generic/ChangeMoveSelector.java | 6 +- .../generic/ChangeMoveSelectorFactory.java | 11 - .../generic/PillarChangeMoveSelector.java | 6 +- .../move/generic/PillarSwapMoveSelector.java | 6 +- .../generic/RuinRecreateMoveIterator.java | 19 +- .../generic/RuinRecreateMoveSelector.java | 2 +- ...Move.java => SelectorBasedChangeMove.java} | 51 +- ...ava => SelectorBasedPillarChangeMove.java} | 56 +- ....java => SelectorBasedPillarSwapMove.java} | 77 +- ...ava => SelectorBasedRuinRecreateMove.java} | 63 +- ...apMove.java => SelectorBasedSwapMove.java} | 63 +- .../move/generic/SwapMoveSelector.java | 6 +- .../move/generic/SwapMoveSelectorFactory.java | 11 - .../generic/list/ListChangeMoveSelector.java | 2 +- .../generic/list/ListSwapMoveSelector.java | 2 +- .../list/OriginalListChangeIterator.java | 13 +- .../list/OriginalListSwapIterator.java | 32 +- .../list/RandomListChangeIterator.java | 2 +- .../generic/list/RandomListSwapIterator.java | 2 +- .../list/RandomSubListChangeMoveIterator.java | 6 +- .../list/RandomSubListChangeMoveSelector.java | 2 +- .../list/RandomSubListSwapMoveSelector.java | 5 +- ....java => SelectorBasedListAssignMove.java} | 43 +- ....java => SelectorBasedListChangeMove.java} | 77 +- ...ve.java => SelectorBasedListSwapMove.java} | 62 +- ...ava => SelectorBasedListUnassignMove.java} | 43 +- ...va => SelectorBasedSubListChangeMove.java} | 70 +- ...java => SelectorBasedSubListSwapMove.java} | 101 +- ... => SelectorBasedSubListUnassignMove.java} | 47 +- .../generic/list/kopt/FlipSublistAction.java | 3 +- .../generic/list/kopt/KOptDescriptor.java | 12 +- .../list/kopt/KOptListMoveIterator.java | 13 +- .../list/kopt/KOptListMoveSelector.java | 2 +- ...ve.java => SelectorBasedKOptListMove.java} | 62 +- ....java => SelectorBasedTwoOptListMove.java} | 109 +- .../ruin/ListRuinRecreateMoveIterator.java | 17 +- .../ruin/ListRuinRecreateMoveSelector.java | 2 +- ...=> SelectorBasedListRuinRecreateMove.java} | 78 +- .../decider/acceptor/Acceptor.java | 2 +- .../solver/core/impl/move/MoveDirector.java | 3 +- .../VariableChangeRecordingScoreDirector.java | 4 +- .../MoveSelectorBasedMoveRepository.java | 16 +- .../NeighborhoodsMoveSelector.java | 6 +- .../stream/BiOriginalMoveIterator.java | 3 +- .../stream/BiRandomMoveIterator.java | 3 +- .../score/constraint/DefaultIndictment.java | 2 +- .../impl/score/director/ValueRangeState.java | 9 +- .../VariableDescriptorAwareScoreDirector.java | 4 +- .../stream/DefaultConstraintMetaModel.java | 5 +- .../bavet/BavetConstraintSessionFactory.java | 4 +- .../common/inliner/AbstractScoreInliner.java | 4 +- .../core/impl/solver/AssignmentProcessor.java | 8 +- .../core/impl/util/CollectionUtils.java | 39 +- .../solver/core/preview/api/move/Move.java | 17 +- .../preview/api/move/builtin/ChangeMove.java | 6 +- .../api/move/builtin/CompositeMove.java | 12 +- .../api/move/builtin/ListAssignMove.java | 6 +- .../api/move/builtin/ListChangeMove.java | 11 +- .../api/move/builtin/ListSwapMove.java | 8 +- .../api/move/builtin/ListUnassignMove.java | 12 +- .../preview/api/move/builtin/SwapMove.java | 6 +- .../core/config/solver/SolverConfigTest.java | 9 +- .../placer/entity/PlacementAssertions.java | 20 +- .../placer/entity/PooledEntityPlacerTest.java | 7 +- .../variable/ListVariableListenerTest.java | 34 +- .../heuristic/move/CompositeMoveTest.java | 189 --- .../core/impl/heuristic/move/DummyMove.java | 55 - .../move/SelectorBasedCompositeMoveTest.java | 192 +++ .../move/SelectorBasedDummyMove.java | 54 + ...ava => SelectorBasedNoChangeMoveTest.java} | 8 +- ...a => SelectorBasedNotDoableDummyMove.java} | 9 +- .../heuristic/selector/SelectorTestUtils.java | 2 +- .../move/MoveSelectorFactoryTest.java | 135 +- .../CartesianProductMoveSelectorTest.java | 34 +- .../move/composite/UnionMoveSelectorTest.java | 16 +- .../decorator/CachingMoveSelectorTest.java | 6 +- .../decorator/FilteringMoveSelectorTest.java | 8 +- .../ProbabilityMoveSelectorTest.java | 7 +- .../SelectedCountLimitMoveSelectorTest.java | 7 +- .../decorator/ShufflingMoveSelectorTest.java | 4 +- .../decorator/SortingMoveSelectorTest.java | 12 +- ....java => SelectorBasedChangeMoveTest.java} | 56 +- ...=> SelectorBasedPillarChangeMoveTest.java} | 60 +- ...a => SelectorBasedPillarSwapMoveTest.java} | 71 +- ...=> SelectorBasedRuinRecreateMoveTest.java} | 34 +- ...st.java => SelectorBasedSwapMoveTest.java} | 77 +- .../move/generic/SwapMoveSelectorTest.java | 2 +- .../list/ListSwapMoveSelectorTest.java | 4 +- ...a => SelectorBasedListAssignMoveTest.java} | 24 +- ...a => SelectorBasedListChangeMoveTest.java} | 84 +- ...electorBasedListRuinRecreateMoveTest.java} | 41 +- ...ava => SelectorBasedListSwapMoveTest.java} | 70 +- ...=> SelectorBasedListUnassignMoveTest.java} | 28 +- ...> SelectorBasedSubListChangeMoveTest.java} | 142 +- ... => SelectorBasedSubListSwapMoveTest.java} | 159 +- .../list/kopt/KOptListMoveIteratorTest.java | 153 +- ...ava => SelectorBasedKOptListMoveTest.java} | 42 +- ...a => SelectorBasedTwoOptListMoveTest.java} | 238 ++- .../AcceptedLocalSearchForagerTest.java | 4 +- .../core/impl/move/MoveDirectorTest.java | 34 +- .../core/impl/solver/DefaultSolverTest.java | 245 ++-- .../core/impl/solver/SolverMetricsIT.java | 6 +- .../preview/api/move/builtin/DummyMove.java | 6 +- .../MixedCustomMoveIteratorFactory.java | 18 +- .../singleentity/MixedCustomPhaseCommand.java | 3 +- .../solver/core/testutil/CodeAssertable.java | 210 +-- .../core/testutil/PlannerTestUtils.java | 8 +- docs/src/modules/ROOT/nav.adoc | 1 + .../constraints-and-score/performance.adoc | 2 +- .../enterprise-edition.adoc | 2 +- .../pages/integration/config-properties.adoc | 2 +- .../.optimization-algorithms.adoc | 1 + .../construction-heuristics.adoc | 14 +- .../move-selector-reference.adoc | 1074 +++++++++++++- .../neighborhoods.adoc | 9 +- .../optimization-algorithms/overview.adoc | 1294 +---------------- .../hello-world/hello-world-quickstart.adoc | 4 +- .../responding-to-change.adoc | 10 +- .../modeling-planning-problems.adoc | 6 +- ...ldProcessorGeneratedGizmoSupplierTest.java | 17 +- ...edMoveTypeBestScoreDiffStatisticPoint.java | 6 +- ...edMoveTypeStepScoreDiffStatisticPoint.java | 6 +- 180 files changed, 3372 insertions(+), 4073 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractMove.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSimplifiedMove.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractUndoMove.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/Move.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewIteratorAdapter.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewMoveAdapter.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMove.java rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/{CompositeMove.java => SelectorBasedCompositeMove.java} (54%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{ChangeMove.java => SelectorBasedChangeMove.java} (56%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{PillarChangeMove.java => SelectorBasedPillarChangeMove.java} (59%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{PillarSwapMove.java => SelectorBasedPillarSwapMove.java} (65%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{RuinRecreateMove.java => SelectorBasedRuinRecreateMove.java} (61%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{SwapMove.java => SelectorBasedSwapMove.java} (63%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListAssignMove.java => SelectorBasedListAssignMove.java} (58%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListChangeMove.java => SelectorBasedListChangeMove.java} (65%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListSwapMove.java => SelectorBasedListSwapMove.java} (69%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListUnassignMove.java => SelectorBasedListUnassignMove.java} (58%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{SubListChangeMove.java => SelectorBasedSubListChangeMove.java} (68%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{SubListSwapMove.java => SelectorBasedSubListSwapMove.java} (60%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{SubListUnassignMove.java => SelectorBasedSubListUnassignMove.java} (59%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/{KOptListMove.java => SelectorBasedKOptListMove.java} (76%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/{TwoOptListMove.java => SelectorBasedTwoOptListMove.java} (78%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/{ListRuinRecreateMove.java => SelectorBasedListRuinRecreateMove.java} (74%) delete mode 100644 core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMoveTest.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/DummyMove.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMoveTest.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedDummyMove.java rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/{NoChangeMoveTest.java => SelectorBasedNoChangeMoveTest.java} (65%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/{NotDoableDummyMove.java => SelectorBasedNotDoableDummyMove.java} (58%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{ChangeMoveTest.java => SelectorBasedChangeMoveTest.java} (69%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{PillarChangeMoveTest.java => SelectorBasedPillarChangeMoveTest.java} (75%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{PillarSwapMoveTest.java => SelectorBasedPillarSwapMoveTest.java} (81%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{RuinRecreateMoveTest.java => SelectorBasedRuinRecreateMoveTest.java} (73%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/{SwapMoveTest.java => SelectorBasedSwapMoveTest.java} (74%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListAssignMoveTest.java => SelectorBasedListAssignMoveTest.java} (82%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListChangeMoveTest.java => SelectorBasedListChangeMoveTest.java} (71%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListRuinRecreateMoveTest.java => SelectorBasedListRuinRecreateMoveTest.java} (70%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListSwapMoveTest.java => SelectorBasedListSwapMoveTest.java} (70%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{ListUnassignMoveTest.java => SelectorBasedListUnassignMoveTest.java} (74%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{SubListChangeMoveTest.java => SelectorBasedSubListChangeMoveTest.java} (58%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/{SubListSwapMoveTest.java => SelectorBasedSubListSwapMoveTest.java} (58%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/{KOptListMoveTest.java => SelectorBasedKOptListMoveTest.java} (97%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/{TwoOptListMoveTest.java => SelectorBasedTwoOptListMoveTest.java} (55%) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java index df604b1c0f7..f66a53cc83f 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java @@ -7,8 +7,8 @@ import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java index 7537881ae90..0dc1e6db10c 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.change.ProblemChange; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; /** * Specifies that a bean property (or a field) is the id to match diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/analysis/ConstraintAnalysis.java b/core/src/main/java/ai/timefold/solver/core/api/score/analysis/ConstraintAnalysis.java index ac41dbdeadc..671e1378193 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/analysis/ConstraintAnalysis.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/analysis/ConstraintAnalysis.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -14,7 +15,6 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintRef; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; import ai.timefold.solver.core.api.solver.SolutionManager; -import ai.timefold.solver.core.impl.util.CollectionUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -154,8 +154,9 @@ private static int getMatchCount(ConstraintAnalysis analysis, ConstraintAnaly private static > Map> mapMatchesToJustifications(List> matchAnalyses) { + Map> matchAnalysisMap = - CollectionUtils.newLinkedHashMap(matchAnalyses.size()); + LinkedHashMap.newLinkedHashMap(matchAnalyses.size()); for (var matchAnalysis : matchAnalyses) { var previous = matchAnalysisMap.put(matchAnalysis.justification(), matchAnalysis); if (previous != null) { diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChange.java b/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChange.java index 0aec2c89ef9..d00518e0558 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChange.java +++ b/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChange.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.solver.Solver; import ai.timefold.solver.core.api.solver.event.BestSolutionChangedEvent; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/EnvironmentMode.java b/core/src/main/java/ai/timefold/solver/core/config/solver/EnvironmentMode.java index bf7c723d651..eeaf1b189e5 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/EnvironmentMode.java +++ b/core/src/main/java/ai/timefold/solver/core/config/solver/EnvironmentMode.java @@ -9,8 +9,8 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.ShadowVariable; import ai.timefold.solver.core.api.solver.Solver; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; /** * The environment mode also allows you to detect common bugs in your implementation. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractFlattenNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractFlattenNode.java index 56186fdf355..aac78956af5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractFlattenNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractFlattenNode.java @@ -12,7 +12,6 @@ import ai.timefold.solver.core.impl.bavet.common.tuple.Tuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleState; -import ai.timefold.solver.core.impl.util.CollectionUtils; public abstract class AbstractFlattenNode extends AbstractNode @@ -124,7 +123,8 @@ private record FlattenBagByItem( } FlattenBagByItem(int size) { - this(CollectionUtils.newLinkedHashMap(size)); + + this(LinkedHashMap.newLinkedHashMap(size)); } /** diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/RecordAndReplayPropagator.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/RecordAndReplayPropagator.java index 7b38d8b9f5f..81c56d24a3a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/RecordAndReplayPropagator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/RecordAndReplayPropagator.java @@ -50,7 +50,7 @@ public RecordAndReplayPropagator(Supplier> pr UnaryOperator internalTupleToOutputTupleMapper, TupleLifecycle nextNodesTupleLifecycle, int size) { this.precomputeBuildHelperSupplier = precomputeBuildHelperSupplier; this.internalTupleToOutputTupleMapper = internalTupleToOutputTupleMapper; - this.objectToOutputTuplesMap = CollectionUtils.newIdentityHashMap(size); + this.objectToOutputTuplesMap = new IdentityHashMap<>(size); // Guesstimate that updates are dominant. this.retractQueue = CollectionUtils.newIdentityHashSet(size / 20); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/decider/ConstructionHeuristicDecider.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/decider/ConstructionHeuristicDecider.java index 139a20486e5..fbdcfcc49e5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/decider/ConstructionHeuristicDecider.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/decider/ConstructionHeuristicDecider.java @@ -9,7 +9,8 @@ import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicPhaseScope; import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicStepScope; import ai.timefold.solver.core.impl.heuristic.move.MoveAdapters; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.phase.scope.SolverLifecyclePoint; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; @@ -130,8 +131,8 @@ public void decideNextStep(ConstructionHeuristicStepScope stepScope, } private static boolean isAllowedNonDoableMove(Move move) { - return MoveAdapters.testWhenLegacyMove(move, legacyMove -> legacyMove instanceof NoChangeMove - || legacyMove instanceof ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove); + return MoveAdapters.testWhenLegacyMove(move, legacyMove -> legacyMove instanceof SelectorBasedNoChangeMove + || legacyMove instanceof SelectorBasedChangeMove); } protected void pickMove(ConstructionHeuristicStepScope stepScope) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/Placement.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/Placement.java index 17e9eb89d70..fd4f3d983bf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/Placement.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/Placement.java @@ -10,12 +10,6 @@ */ public class Placement implements Iterable> { - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static Iterator> - toNewMoveIterator(Iterator> legacyIterator) { - return (Iterator) legacyIterator; - } - private final Iterator> moveIterator; public Placement(Iterator> moveIterator) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/PooledEntityPlacer.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/PooledEntityPlacer.java index 46186dda7ad..0fd8fea90a2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/PooledEntityPlacer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/PooledEntityPlacer.java @@ -3,11 +3,11 @@ import java.util.Iterator; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class PooledEntityPlacer extends AbstractEntityPlacer implements EntityPlacer { @@ -41,7 +41,7 @@ protected Placement createUpcomingSelection() { if (!moveIterator.hasNext()) { return noUpcomingSelection(); } - return new Placement<>(Placement.toNewMoveIterator(moveIterator)); + return new Placement<>(moveIterator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacer.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacer.java index 8354c672a23..5622bd5ccde 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacer.java @@ -5,12 +5,12 @@ import java.util.List; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class QueuedEntityPlacer extends AbstractEntityPlacer implements EntityPlacer { @@ -66,7 +66,7 @@ protected Placement createUpcomingSelection() { MoveSelector moveSelector = moveSelectorIterator.next(); moveIterator = moveSelector.iterator(); } - return new Placement<>(Placement.toNewMoveIterator(moveIterator)); + return new Placement<>(moveIterator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java index 2c3031831f6..6193ac0b082 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java @@ -58,7 +58,7 @@ protected Placement createUpcomingSelection() { if (!moveIterator.hasNext()) { return noUpcomingSelection(); } - return new Placement<>(Placement.toNewMoveIterator(moveIterator)); + return new Placement<>(moveIterator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java index 8ced593a76e..6a79286449b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java @@ -60,7 +60,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; import ai.timefold.solver.core.impl.move.MoveDirector; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.MutableInt; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel; import ai.timefold.solver.core.preview.api.neighborhood.stream.function.UniNeighborhoodsPredicate; @@ -486,7 +485,8 @@ Maybe remove the variables (%s) from the class (%s).""".formatted(entityClass, redefinedShadowVariables, redefinedShadowVariables, entityClass)); } effectiveShadowVariableDescriptorMap.putAll(declaredShadowVariableDescriptorMap); - effectiveVariableDescriptorMap = CollectionUtils + + effectiveVariableDescriptorMap = LinkedHashMap .newLinkedHashMap(effectiveGenuineVariableDescriptorMap.size() + effectiveShadowVariableDescriptorMap.size()); effectiveVariableDescriptorMap.putAll(effectiveGenuineVariableDescriptorMap); effectiveVariableDescriptorMap.putAll(effectiveShadowVariableDescriptorMap); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/FieldAccessingSolutionCloner.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/FieldAccessingSolutionCloner.java index 03cbfb394bc..3fce08744c6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/FieldAccessingSolutionCloner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/FieldAccessingSolutionCloner.java @@ -11,6 +11,9 @@ import java.util.Deque; import java.util.EnumMap; import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -28,7 +31,6 @@ import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.ConcurrentMemoization; import org.jspecify.annotations.NonNull; @@ -59,7 +61,7 @@ public FieldAccessingSolutionCloner(SolutionDescriptor solutionDescri @Override public @NonNull Solution_ cloneSolution(@NonNull Solution_ originalSolution) { var expectedObjectCount = expectedObjectCountRef.get(); - var originalToCloneMap = CollectionUtils.newIdentityHashMap(expectedObjectCount); + var originalToCloneMap = new IdentityHashMap<>(expectedObjectCount); var unprocessedQueue = new ArrayDeque(expectedObjectCount); var cloneSolution = clone(originalSolution, originalToCloneMap, unprocessedQueue, retrieveClassMetadata(originalSolution.getClass())); @@ -235,9 +237,9 @@ public static Collection constructCloneCollection(Collection originalC } else if (!(originalCollection instanceof LinkedHashSet)) { // Set is explicitly not ordered, so we can use a HashSet. // Can be replaced by checking for SequencedSet, but that is Java 21+. - return CollectionUtils.newHashSet(size); + return HashSet.newHashSet(size); } else { // Default to a LinkedHashSet to respect order. - return CollectionUtils.newLinkedHashSet(size); + return LinkedHashSet.newLinkedHashSet(size); } } else if (originalCollection instanceof Deque) { return new ArrayDeque<>(size); @@ -298,9 +300,10 @@ public static Map constructCloneMap(Map originalMap) { if (!(originalMap instanceof LinkedHashMap)) { // Map is explicitly not ordered, so we can use a HashMap. // Can be replaced by checking for SequencedMap, but that is Java 21+. - return CollectionUtils.newHashMap(originalMapSize); + return HashMap.newHashMap(originalMapSize); } else { // Default to a LinkedHashMap to respect order. - return CollectionUtils.newLinkedHashMap(originalMapSize); + + return LinkedHashMap.newLinkedHashMap(originalMapSize); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/ValueRangeCache.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/ValueRangeCache.java index 558a48a2379..5494410a22d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/ValueRangeCache.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/ValueRangeCache.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -9,7 +10,6 @@ import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator; -import ai.timefold.solver.core.impl.util.CollectionUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -19,11 +19,11 @@ public final class ValueRangeCache implements Iterable { public static ValueRangeCache of(int size) { - return new ValueRangeCache<>(size, CollectionUtils.newHashSet(size)); + return new ValueRangeCache<>(size, HashSet.newHashSet(size)); } public static ValueRangeCache of(Collection collection) { - return new ValueRangeCache<>(collection, CollectionUtils.newHashSet(collection.size())); + return new ValueRangeCache<>(collection, HashSet.newHashSet(collection.size())); } public static ValueRangeCache of(List valuesWithFastRandomAccess, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java index 8573701740c..548ca8b81f0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java @@ -1,5 +1,6 @@ package ai.timefold.solver.core.impl.domain.variable; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -61,7 +62,7 @@ public void initialize(InnerScoreDirector scoreDirector, int initi || externalizedPreviousElementProcessor == null || externalizedNextElementProcessor == null; if (requiresPositionMap) { if (elementPositionMap == null) { - elementPositionMap = CollectionUtils.newIdentityHashMap(unassignedCount); + elementPositionMap = new IdentityHashMap<>(unassignedCount); } else { elementPositionMap.clear(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DefaultTopologicalOrderGraph.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DefaultTopologicalOrderGraph.java index 84b5a624b1f..0ec94f80639 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DefaultTopologicalOrderGraph.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DefaultTopologicalOrderGraph.java @@ -3,13 +3,13 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.PrimitiveIterator; import java.util.Set; import java.util.stream.Collectors; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.MutableInt; public class DefaultTopologicalOrderGraph implements TopologicalOrderGraph { @@ -23,7 +23,8 @@ public class DefaultTopologicalOrderGraph implements TopologicalOrderGraph { @SuppressWarnings({ "unchecked" }) public DefaultTopologicalOrderGraph(final int size) { this.nodeIdToTopologicalOrderMap = new int[size]; - this.componentMap = CollectionUtils.newLinkedHashMap(size); + + this.componentMap = LinkedHashMap.newLinkedHashMap(size); this.forwardEdges = new Set[size]; this.backEdges = new Set[size]; this.isNodeInLoopedComponent = new boolean[size]; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractMove.java deleted file mode 100644 index 7358522aabc..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractMove.java +++ /dev/null @@ -1,102 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.valuerange.ValueRange; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.domain.valuerange.descriptor.AbstractValueRangeDescriptor; -import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector; -import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; -import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; - -/** - * Abstract superclass for {@link Move}, requiring implementation of undo moves. - * - * @param the solution type, the class with the {@link PlanningSolution} annotation - * @see Move - */ -public abstract class AbstractMove implements Move { - - @Override - public final void doMoveOnly(ScoreDirector scoreDirector) { - var recordingScoreDirector = - scoreDirector instanceof VariableChangeRecordingScoreDirector variableChangeRecordingScoreDirector - ? variableChangeRecordingScoreDirector - : new VariableChangeRecordingScoreDirector<>(scoreDirector); - doMoveOnGenuineVariables(recordingScoreDirector); - scoreDirector.triggerVariableListeners(); - } - - /** - * Called before the move is done, so the move can be evaluated and then be undone - * without resulting into a permanent change in the solution. - * - * @param scoreDirector the {@link ScoreDirector} not yet modified by the move. - * @return an undoMove which does the exact opposite of this move. - * @deprecated The solver automatically generates undo moves, this method is no longer used. - */ - @Deprecated(forRemoval = true, since = "1.16.0") - protected Move createUndoMove(ScoreDirector scoreDirector) { - throw new UnsupportedOperationException("Operation requires an undo move, which is no longer supported."); - } - - /** - * Like {@link #doMoveOnly(ScoreDirector)} but without the {@link ScoreDirector#triggerVariableListeners()} call - * (because {@link #doMoveOnly(ScoreDirector)} already does that). - * - * @param scoreDirector never null - */ - protected abstract void doMoveOnGenuineVariables(ScoreDirector scoreDirector); - - protected ValueRange extractValueRangeFromEntity(ScoreDirector scoreDirector, - AbstractValueRangeDescriptor valueRangeDescriptor, Object entity) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - return castScoreDirector.getValueRangeManager() - .getFromEntity(valueRangeDescriptor, entity); - } - - // ************************************************************************ - // Util methods - // ************************************************************************ - - public static List rebaseList(List externalObjectList, ScoreDirector destinationScoreDirector) { - var rebasedObjectList = new ArrayList(externalObjectList.size()); - for (var entity : externalObjectList) { - rebasedObjectList.add(destinationScoreDirector.lookUpWorkingObject(entity)); - } - return rebasedObjectList; - } - - public static Set rebaseSet(Set externalObjectSet, ScoreDirector destinationScoreDirector) { - var rebasedObjectSet = new LinkedHashSet(externalObjectSet.size()); - for (var entity : externalObjectSet) { - rebasedObjectSet.add(destinationScoreDirector.lookUpWorkingObject(entity)); - } - return rebasedObjectSet; - } - - // ************************************************************************ - // Final methods from the new move interface, to prevent user error - // ************************************************************************ - - @Override - public final void execute(MutableSolutionView solutionView) { - Move.super.execute(solutionView); - } - - @Override - public final ai.timefold.solver.core.preview.api.move.Move rebase(Rebaser rebaser) { - return Move.super.rebase(rebaser); - } - - @Override - public final String describe() { - return Move.super.describe(); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java new file mode 100644 index 00000000000..d4a960e0b69 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java @@ -0,0 +1,101 @@ +package ai.timefold.solver.core.impl.heuristic.move; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.SequencedSet; +import java.util.Set; + +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.valuerange.ValueRange; +import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.domain.valuerange.descriptor.AbstractValueRangeDescriptor; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedSwapMove; +import ai.timefold.solver.core.impl.move.MoveDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; +import ai.timefold.solver.core.preview.api.neighborhood.Neighborhood; + +import org.jspecify.annotations.NullMarked; + +/** + * Abstract superclass for {@link Move}, + * intended to grandfather in the legacy {@link Move} implementations that are supplied by move selectors. + * These legacy move selectors are built around the idea + * of {@link #isMoveDoable(ScoreDirector) non-doable moves} being generated + * and only later filtered out by the solver. + * This class exists for the benefit of such move selectors. + *

+ * These days moves are built around the idea of only generating doable moves, + * and nothing prevents new move selectors from being written in that way as well. + * In that case, these move implementations can simply extend {@link Move} directly + * and not bother with {@link AbstractSelectorBasedMove} at all. + * However, users are encouraged not to implement any new move selectors anymore, + * and instead use the {@link Neighborhood Neighborhoods} API to implements their custom moves. + *

+ * Moves that extend this class are expected to be selector-based, + * and should be named with the "SelectorBased" prefix, + * like {@link SelectorBasedChangeMove} and {@link SelectorBasedSwapMove}. + * This is to quickly distinguish them in stack traces and IDEs from other moves that do not extend this class + * + * @param the solution type, the class with the {@link PlanningSolution} annotation + * @see Move + */ +@NullMarked +public abstract class AbstractSelectorBasedMove implements Move { + + public boolean isMoveDoable(ScoreDirector scoreDirector) { + return true; + } + + @Override + public final void execute(MutableSolutionView solutionView) { + var scoreDirector = ((MoveDirector) solutionView).getScoreDirector(); + execute(scoreDirector); + scoreDirector.triggerVariableListeners(); + } + + protected abstract void execute(VariableDescriptorAwareScoreDirector scoreDirector); + + @Override + public String describe() { + // Do not include the "SelectorBased" prefix in the description, + // as that is just an implementation detail that is not relevant to the user. + var name = getClass().getSimpleName(); + if (name.startsWith("SelectorBased")) { + return name.substring("SelectorBased".length()); + } + return name; + } + + protected ValueRange extractValueRangeFromEntity(ScoreDirector scoreDirector, + AbstractValueRangeDescriptor valueRangeDescriptor, Object entity) { + var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + return castScoreDirector.getValueRangeManager() + .getFromEntity(valueRangeDescriptor, entity); + } + + // ************************************************************************ + // Util methods + // ************************************************************************ + + public static List rebaseList(List externalObjectList, Rebaser rebaser) { + var rebasedObjectList = new ArrayList(externalObjectList.size()); + for (var entity : externalObjectList) { + rebasedObjectList.add(rebaser.rebase(entity)); + } + return rebasedObjectList; + } + + public static SequencedSet rebaseSet(Set externalObjectSet, Rebaser rebaser) { + var rebasedObjectSet = LinkedHashSet. newLinkedHashSet(externalObjectSet.size()); + for (var entity : externalObjectSet) { + rebasedObjectSet.add(rebaser.rebase(entity)); + } + return rebasedObjectSet; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSimplifiedMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSimplifiedMove.java deleted file mode 100644 index 4f930d9a152..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSimplifiedMove.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector; - -/** - * This is an alternative to {@link AbstractMove}, - * allowing to trade some performance for less boilerplate. - * This move will record all events that change variables, - * and replay them in the undo move, - * therefore removing the need to implement the undo move. - * - * @param - * @deprecated In favor of {@link AbstractMove}, which no longer requires undo moves to be implemented either. - */ -@Deprecated(forRemoval = true, since = "1.16.0") -public abstract class AbstractSimplifiedMove implements Move { - - @Override - public final void doMoveOnly(ScoreDirector scoreDirector) { - var recordingScoreDirector = - scoreDirector instanceof VariableChangeRecordingScoreDirector variableChangeRecordingScoreDirector - ? variableChangeRecordingScoreDirector - : new VariableChangeRecordingScoreDirector<>(scoreDirector); - doMoveOnGenuineVariables(recordingScoreDirector); - recordingScoreDirector.triggerVariableListeners(); - } - - protected abstract void doMoveOnGenuineVariables(ScoreDirector scoreDirector); - - @Override - public String toString() { - return getSimpleMoveTypeDescription(); - } - -} \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractUndoMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractUndoMove.java deleted file mode 100644 index d2d990ddeed..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractUndoMove.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Collection; -import java.util.Objects; - -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.score.director.ScoreDirector; - -/** - * Abstract superclass for {@link Move}, suggested starting point to implement undo moves. - * - * @param the solution type, the class with the {@link PlanningSolution} annotation - * @see Move - * @deprecated Undo moves are automatically generated by the solver. - * Implementations of this class no longer have any effect any may be removed. - */ -@Deprecated(forRemoval = true, since = "1.16.0") -public abstract class AbstractUndoMove implements Move { - - protected final Move parentMove; - - protected AbstractUndoMove(Move parentMove) { - this.parentMove = Objects.requireNonNull(parentMove); - } - - @Override - public final boolean isMoveDoable(ScoreDirector scoreDirector) { - return true; // Undo moves are always doable; the parent move was already done. - } - - @Override - public final void doMoveOnly(ScoreDirector scoreDirector) { - doMoveOnGenuineVariables(scoreDirector); - scoreDirector.triggerVariableListeners(); - } - - /** - * Like {@link #doMoveOnly(ScoreDirector)} but without the {@link ScoreDirector#triggerVariableListeners()} call - * (because {@link #doMoveOnly(ScoreDirector)} already does that). - * - * @param scoreDirector never null - */ - protected abstract void doMoveOnGenuineVariables(ScoreDirector scoreDirector); - - @Override - public final Collection getPlanningEntities() { - return parentMove.getPlanningEntities(); - } - - @Override - public final Collection getPlanningValues() { - return parentMove.getPlanningValues(); - } - - @Override - public String getSimpleMoveTypeDescription() { - return "Undo(" + parentMove.getSimpleMoveTypeDescription() + ")"; - } - - @Override - public String toString() { - return getSimpleMoveTypeDescription(); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/Move.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/Move.java deleted file mode 100644 index 98b348743ad..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/Move.java +++ /dev/null @@ -1,204 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Iterator; - -import ai.timefold.solver.core.api.domain.common.PlanningId; -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.solution.ProblemFactProperty; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.api.solver.Solver; -import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; -import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveListFactory; -import ai.timefold.solver.core.impl.localsearch.decider.acceptor.tabu.MoveTabuAcceptor; -import ai.timefold.solver.core.impl.move.MoveDirector; -import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; - -/** - * A Move represents a change of 1 or more {@link PlanningVariable}s of 1 or more {@link PlanningEntity}s - * in the working {@link PlanningSolution}. - *

- * Usually the move holds a direct reference to each {@link PlanningEntity} of the {@link PlanningSolution} - * which it will change when {@link #doMoveOnly(ScoreDirector)} is called. - * On that change it should also notify the {@link ScoreDirector} accordingly. - *

- * A Move should implement {@link Object#equals(Object)} and {@link Object#hashCode()} for {@link MoveTabuAcceptor}. - *

- * An implementation must extend {@link AbstractMove} to ensure backwards compatibility in future versions. - * It is highly recommended to override {@link #getPlanningEntities()} and {@link #getPlanningValues()}, - * otherwise the resulting move will throw an exception when used with Tabu search. - *

- * To ease interoperability with Neighborhoods API, - * this interface extends {@link ai.timefold.solver.core.preview.api.move.Move}, - * giving the user an option to override certain methods which they should not. - * Specifically, the following methods must not be overridden by the user, - * as suitable default implementations are provided: - * - *

    - *
  • {@link #execute(MutableSolutionView)}
  • - *
  • {@link #rebase(Rebaser)}
  • - *
  • {@link #describe()}
  • - *
- * - * The following methods should also not be overridden, on account of being deprecated: - * - *
    - *
  • {@link #doMove(ScoreDirector)}
  • - *
- * - *

- * This entire interface exists to provide interoperability with move selectors - * and will eventually be phased out in favor of the Neighborhoods API. - * It will be marked as deprecated for removal in a future release. - * - *

- * To avoid having to implement this interface and instead use the new Move API directly, - * you can use {@link MoveAdapters#toLegacyMoveIterator(Iterator)} in your move selectors. - * - * @param the solution type, the class with the {@link PlanningSolution} annotation - * @see AbstractMove - */ -public interface Move extends ai.timefold.solver.core.preview.api.move.Move { - - /** - * Called before a move is evaluated to decide whether the move can be done and evaluated. - * A Move is not doable if: - *

    - *
  • Either doing it would change nothing in the {@link PlanningSolution}.
  • - *
  • Either it's simply not possible to do (for example due to built-in hard constraints).
  • - *
- *

- * It is recommended to keep this method implementation simple: do not use it in an attempt to satisfy normal - * hard and soft constraints. - *

- * Although you could also filter out non-doable moves in for example the {@link MoveSelector} - * or {@link MoveListFactory}, this is not needed as the {@link Solver} will do it for you. - * - * @param scoreDirector the {@link ScoreDirector} not yet modified by the move. - * @return true if the move achieves a change in the solution and the move is possible to do on the solution. - */ - boolean isMoveDoable(ScoreDirector scoreDirector); - - /** - * Does the move (which indirectly affects the {@link ScoreDirector#getWorkingSolution()}). - * When the {@link PlanningSolution working solution} is modified, the {@link ScoreDirector} must be correctly notified - * (through {@link ScoreDirector#beforeVariableChanged(Object, String)} and - * {@link ScoreDirector#afterVariableChanged(Object, String)}), - * otherwise later calculated {@link Score}s will be corrupted. - *

- * This method must end with calling {@link ScoreDirector#triggerVariableListeners()} to ensure all shadow variables are - * updated. - *

- * This method must return an undo move, so the move can be evaluated and then be undone - * without resulting into a permanent change in the solution. - * - * @param scoreDirector never null, the {@link ScoreDirector} that needs to get notified of the changes - * @return an undoMove which does the exact opposite of this move - * @deprecated Prefer {@link #doMoveOnly(ScoreDirector)} instead, undo moves no longer have any effect. - */ - @Deprecated(forRemoval = true, since = "1.16.0") - default Move doMove(ScoreDirector scoreDirector) { - throw new UnsupportedOperationException("Operation requires an undo move, which is no longer supported."); - } - - /** - * Does the move (which indirectly affects the {@link ScoreDirector#getWorkingSolution()}). - * When the {@link PlanningSolution working solution} is modified, - * the {@link ScoreDirector} must be correctly notified - * (through {@link ScoreDirector#beforeVariableChanged(Object, String)} and - * {@link ScoreDirector#afterVariableChanged(Object, String)}), - * otherwise later calculated {@link Score}s will be corrupted, - * or the move may not be correctly undone. - *

- * This method must end with calling {@link ScoreDirector#triggerVariableListeners()} - * to ensure all shadow variables are updated. - * - * @param scoreDirector never null, the {@link ScoreDirector} that needs to get notified of the changes - */ - default void doMoveOnly(ScoreDirector scoreDirector) { - // For backwards compatibility, this method is default and calls doMove(...). - // Normally, the relationship is inverted, as implemented in AbstractMove. - doMove(scoreDirector); - } - - /** - * Do not override this default implementation. - */ - @Override - default void execute(MutableSolutionView solutionView) { - var scoreDirector = ((MoveDirector) solutionView).getScoreDirector(); - doMoveOnly(scoreDirector); - } - - /** - * Rebases a move from an origin {@link ScoreDirector} to another destination {@link ScoreDirector} - * which is usually on another {@link Thread} or JVM. - * The new move returned by this method translates the entities and problem facts - * to the destination {@link PlanningSolution} of the destination {@link ScoreDirector}, - * That destination {@link PlanningSolution} is a deep planning clone (or an even deeper clone) - * of the origin {@link PlanningSolution} that this move has been generated from. - *

- * That new move does the exact same change as this move, - * resulting in the same {@link PlanningSolution} state, - * presuming that destination {@link PlanningSolution} was in the same state - * as the original {@link PlanningSolution} to begin with. - *

- * Generally speaking, an implementation of this method iterates through every entity and fact instance in this move, - * translates each one to the destination {@link ScoreDirector} with {@link ScoreDirector#lookUpWorkingObject(Object)} - * and creates a new move instance of the same move type, using those translated instances. - *

- * The destination {@link PlanningSolution} can be in a different state than the original {@link PlanningSolution}. - * So, rebasing can only depend on the identity of {@link PlanningEntity planning entities} - * and {@link ProblemFactProperty problem facts}, - * which are usually declared by a {@link PlanningId} on those classes. - * It must not depend on the state of the {@link PlanningVariable planning variables}. - * One thread might rebase a move before, amid or after another thread does that same move instance. - *

- * This method is thread-safe. - * - * @param destinationScoreDirector never null, the {@link ScoreDirector#getWorkingSolution()} - * that the new move should change the planning entity instances of. - * @return never null, a new move that does the same change as this move on another solution instance - */ - default Move rebase(ScoreDirector destinationScoreDirector) { - throw new UnsupportedOperationException( - "Move class (%s) doesn't implement the rebase() method, so multithreaded solving is impossible." - .formatted(getClass())); - } - - /** - * Do not override this default implementation. - */ - @Override - default ai.timefold.solver.core.preview.api.move.Move rebase(Rebaser rebaser) { - return rebase(((MoveDirector) rebaser).getScoreDirector()); - } - - // ************************************************************************ - // Introspection methods - // ************************************************************************ - - /** - * Describes the move type for statistical purposes. - * For example "ChangeMove(Process.computer)". - *

- * The format is not formalized. Never parse the {@link String} returned by this method. - * - * @return never null - */ - default String getSimpleMoveTypeDescription() { - return getClass().getSimpleName(); - } - - /** - * Do not override this default implementation. - */ - @Override - default String describe() { - return getSimpleMoveTypeDescription(); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java index fcdfef0fc94..e3e8f86c347 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java @@ -1,10 +1,10 @@ package ai.timefold.solver.core.impl.heuristic.move; -import java.util.Iterator; import java.util.function.Predicate; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.move.MoveDirector; +import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; @@ -16,52 +16,29 @@ @NullMarked public final class MoveAdapters { - static ai.timefold.solver.core.impl.heuristic.move.Move - toLegacyMove(ai.timefold.solver.core.preview.api.move.Move newMove) { - if (newMove instanceof Move legacyMove) { - return legacyMove; - } - return new NewMoveAdapter<>(newMove); - } - - public static ai.timefold.solver.core.preview.api.move.Move - unadapt(ai.timefold.solver.core.preview.api.move.Move possibleLegacyMove) { - if (possibleLegacyMove instanceof NewMoveAdapter newMoveAdapter) { - return newMoveAdapter.newMove(); - } - return possibleLegacyMove; - } - - public static Iterator> - toLegacyMoveIterator(Iterator> newIterator) { - return new NewIteratorAdapter<>(newIterator); - } - /** * Used to determine if a move is doable. * A move is only doable if: * *

    - *
  • It is a new {@link ai.timefold.solver.core.preview.api.move.Move}.
  • - *
  • It is a legacy move and its {@link AbstractMove#isMoveDoable(ScoreDirector)} return {@code true}.
  • + *
  • It is a new {@link Move}.
  • + *
  • It is a legacy move and its {@link AbstractSelectorBasedMove#isMoveDoable(ScoreDirector)} return {@code true}.
  • *
* * @param moveDirector never null * @param move never null * @return true if the move is doable */ - public static boolean isDoable(MoveDirector moveDirector, - ai.timefold.solver.core.preview.api.move.Move move) { - if (move instanceof Move legacyMove) { + public static boolean isDoable(MoveDirector moveDirector, Move move) { + if (move instanceof AbstractSelectorBasedMove legacyMove) { return legacyMove.isMoveDoable(moveDirector.getScoreDirector()); } else { return true; // New moves are always doable. } } - public static boolean testWhenLegacyMove(ai.timefold.solver.core.preview.api.move.Move move, - Predicate> predicate) { - if (move instanceof Move legacyMove) { + public static boolean testWhenLegacyMove(Move move, Predicate> predicate) { + if (move instanceof AbstractSelectorBasedMove legacyMove) { return predicate.test(legacyMove); } else { return false; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewIteratorAdapter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewIteratorAdapter.java deleted file mode 100644 index e22477170c7..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewIteratorAdapter.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Iterator; - -import ai.timefold.solver.core.preview.api.move.Move; - -record NewIteratorAdapter(Iterator> moveIterator) - implements - Iterator> { - - @Override - public boolean hasNext() { - return moveIterator.hasNext(); - } - - @Override - public ai.timefold.solver.core.impl.heuristic.move.Move next() { - return MoveAdapters.toLegacyMove(moveIterator.next()); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewMoveAdapter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewMoveAdapter.java deleted file mode 100644 index a27d1adaa73..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NewMoveAdapter.java +++ /dev/null @@ -1,81 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Collection; -import java.util.Objects; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.move.MoveDirector; -import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.preview.api.move.Move; - -import org.jspecify.annotations.NullMarked; - -/** - * Adapts {@link Move a new move} - * to {@link ai.timefold.solver.core.impl.heuristic.move.Move a legacy move}. - * Once the move selector framework is removed, this may be removed as well. - * - * @param newMove the move to adapt - * @param - */ -@NullMarked -record NewMoveAdapter(Move newMove) - implements - ai.timefold.solver.core.impl.heuristic.move.Move { - - @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return true; // New moves are always doable. - } - - @Override - public void doMoveOnly(ScoreDirector scoreDirector) { - newMove.execute(getMoveDirector(scoreDirector)); - } - - private MoveDirector getMoveDirector(ScoreDirector scoreDirector) { - if (scoreDirector instanceof VariableChangeRecordingScoreDirector recordingScoreDirector) { - return recordingScoreDirector.getBacking().getMoveDirector(); - } - return ((InnerScoreDirector) scoreDirector).getMoveDirector(); - } - - @Override - public ai.timefold.solver.core.impl.heuristic.move.Move - rebase(ScoreDirector destinationScoreDirector) { - return MoveAdapters.toLegacyMove(newMove.rebase(getMoveDirector(destinationScoreDirector))); - } - - @Override - public String getSimpleMoveTypeDescription() { - return newMove.describe(); - } - - @Override - public Collection getPlanningEntities() { - return newMove.getPlanningEntities(); - } - - @Override - public Collection getPlanningValues() { - return newMove.getPlanningValues(); - } - - @Override - public boolean equals(Object o) { - return o instanceof NewMoveAdapter other - && Objects.equals(newMove, other.newMove); - } - - @Override - public int hashCode() { - return Objects.hashCode(newMove); - } - - @Override - public String toString() { - return "Adapted(%s)".formatted(newMove); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMove.java deleted file mode 100644 index 4996a42af3c..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMove.java +++ /dev/null @@ -1,56 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Collection; -import java.util.Collections; - -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.score.director.ScoreDirector; - -/** - * Makes no changes. - * - * @param the solution type, the class with the {@link PlanningSolution} annotation - */ -public final class NoChangeMove extends AbstractMove { - - public static final NoChangeMove INSTANCE = new NoChangeMove<>(); - - public static NoChangeMove getInstance() { - return (NoChangeMove) INSTANCE; - } - - private NoChangeMove() { - // No external instances allowed. - } - - @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return false; - } - - @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - // Do nothing. - } - - @Override - public Move rebase(ScoreDirector destinationScoreDirector) { - return (Move) INSTANCE; - } - - @Override - public Collection getPlanningEntities() { - return Collections.emptyList(); - } - - @Override - public Collection getPlanningValues() { - return Collections.emptyList(); - } - - @Override - public String getSimpleMoveTypeDescription() { - return "No change"; - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java similarity index 54% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java index 5765db5068c..b16acf45ce6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java @@ -1,14 +1,19 @@ package ai.timefold.solver.core.impl.heuristic.move; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; +import java.util.SequencedCollection; import java.util.stream.Collectors; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * A CompositeMove is composed out of multiple other moves. @@ -19,7 +24,8 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation * @see Move */ -public final class CompositeMove extends AbstractMove { +@NullMarked +public final class SelectorBasedCompositeMove extends AbstractSelectorBasedMove { /** * @param moves never null, sometimes empty. Do not modify this argument afterwards or the CompositeMove corrupts. @@ -28,9 +34,9 @@ public final class CompositeMove extends AbstractMove { @SafeVarargs public static > Move buildMove(Move_... moves) { return switch (moves.length) { - case 0 -> NoChangeMove.getInstance(); + case 0 -> SelectorBasedNoChangeMove.getInstance(); case 1 -> moves[0]; - default -> new CompositeMove<>(moves); + default -> new SelectorBasedCompositeMove<>(moves); }; } @@ -38,6 +44,7 @@ public static > Move buildMo * @param moveList never null, sometimes empty * @return never null */ + @SuppressWarnings("unchecked") public static > Move buildMove(List moveList) { return buildMove(moveList.toArray(new Move[0])); } @@ -52,7 +59,7 @@ public static > Move buildMo * @param moves never null, never empty. Do not modify this argument afterwards or this CompositeMove corrupts. */ @SafeVarargs - CompositeMove(Move... moves) { + SelectorBasedCompositeMove(Move... moves) { this.moves = moves; } @@ -62,8 +69,9 @@ public Move[] getMoves() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - for (Move move : moves) { - if (move.isMoveDoable(scoreDirector)) { + for (var move : moves) { + if (move instanceof AbstractSelectorBasedMove legacyMove + && legacyMove.isMoveDoable(scoreDirector)) { return true; } } @@ -71,51 +79,50 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - for (Move move : moves) { - if (!move.isMoveDoable(scoreDirector)) { - continue; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + for (var move : moves) { + if (move instanceof AbstractSelectorBasedMove legacyMove) { + if (legacyMove.isMoveDoable(scoreDirector)) { + legacyMove.execute(scoreDirector); + } + } else { + move.execute(((InnerScoreDirector) scoreDirector).getMoveDirector()); } - // Calls scoreDirector.triggerVariableListeners() between moves - // because a later move can depend on the shadow variables changed by an earlier move - move.doMoveOnly(scoreDirector); } } + @SuppressWarnings("unchecked") @Override - public CompositeMove rebase(ScoreDirector destinationScoreDirector) { - Move[] rebasedMoves = new Move[moves.length]; - for (int i = 0; i < moves.length; i++) { - rebasedMoves[i] = moves[i].rebase(destinationScoreDirector); + public SelectorBasedCompositeMove rebase(Rebaser rebaser) { + var rebasedMoves = new Move[moves.length]; + for (var i = 0; i < moves.length; i++) { + rebasedMoves[i] = moves[i].rebase(rebaser); } - return new CompositeMove<>(rebasedMoves); + return new SelectorBasedCompositeMove(rebasedMoves); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + Arrays.stream(moves) - .map(Move::getSimpleMoveTypeDescription) + public String describe() { + return "CompositeMove" + Arrays.stream(moves) + .map(Move::describe) .sorted() .map(childMoveTypeDescription -> "* " + childMoveTypeDescription) .collect(Collectors.joining(",", "(", ")")); } @Override - public Collection getPlanningEntities() { - Set entities = CollectionUtils.newLinkedHashSet(moves.length * 2); - for (Move move : moves) { + public SequencedCollection getPlanningEntities() { + var entities = LinkedHashSet.newLinkedHashSet(moves.length * 2); + for (var move : moves) { entities.addAll(move.getPlanningEntities()); } return entities; } @Override - public Collection getPlanningValues() { - Set values = CollectionUtils.newLinkedHashSet(moves.length * 2); - for (Move move : moves) { + public SequencedCollection getPlanningValues() { + var values = LinkedHashSet.newLinkedHashSet(moves.length * 2); + for (var move : moves) { values.addAll(move.getPlanningValues()); } return values; @@ -123,7 +130,7 @@ public Collection getPlanningValues() { @Override public boolean equals(Object other) { - return other instanceof CompositeMove otherCompositeMove + return other instanceof SelectorBasedCompositeMove otherCompositeMove && Arrays.equals(moves, otherCompositeMove.moves); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java new file mode 100644 index 00000000000..332c116f192 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java @@ -0,0 +1,58 @@ +package ai.timefold.solver.core.impl.heuristic.move; + +import java.util.Collections; +import java.util.SequencedCollection; + +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; + +/** + * Makes no changes. + * + * @param the solution type, the class with the {@link PlanningSolution} annotation + */ +@NullMarked +public final class SelectorBasedNoChangeMove extends AbstractSelectorBasedMove { + + public static final SelectorBasedNoChangeMove INSTANCE = new SelectorBasedNoChangeMove<>(); + + @SuppressWarnings("unchecked") + public static SelectorBasedNoChangeMove getInstance() { + return (SelectorBasedNoChangeMove) INSTANCE; + } + + private SelectorBasedNoChangeMove() { + // No external instances allowed. + } + + @Override + public boolean isMoveDoable(ScoreDirector scoreDirector) { + return false; + } + + @Override + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + // Do nothing. + } + + @Override + public Move rebase(Rebaser rebaser) { + return getInstance(); + } + + @Override + public SequencedCollection getPlanningEntities() { + return Collections.emptyList(); + } + + @Override + public SequencedCollection getPlanningValues() { + return Collections.emptyList(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/Selector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/Selector.java index 2c78bfa3f95..877606737d7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/Selector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/Selector.java @@ -3,11 +3,11 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListener; +import ai.timefold.solver.core.preview.api.move.Move; /** * General interface for {@link MoveSelector}, {@link EntitySelector} and {@link ValueSelector} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java index 2a87dacf8bb..603f269f737 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java @@ -7,8 +7,8 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import ai.timefold.solver.core.preview.api.move.Move; /** * Decides on accepting or discarding a selection, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java index 4d87e718837..6cb1c6ade3f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java @@ -3,8 +3,8 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import ai.timefold.solver.core.preview.api.move.Move; /** * Create a probabilityWeight for a selection diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorter.java index 73c881b3321..f99a84107f6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorter.java @@ -6,8 +6,8 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 8989c4416bd..47a74e6f310 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -5,8 +5,8 @@ import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import ai.timefold.solver.core.preview.api.move.Move; /** * Creates a weight to decide the order of a collections of selections diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalChangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalChangeIterator.java index 4dead5519e2..7d892ccfbe4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalChangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalChangeIterator.java @@ -3,9 +3,9 @@ import java.util.Collections; import java.util.Iterator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractOriginalChangeIterator> extends UpcomingSelectionIterator { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalSwapIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalSwapIterator.java index 1d1ac90fb93..4ee02db3202 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalSwapIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractOriginalSwapIterator.java @@ -3,8 +3,8 @@ import java.util.Collections; import java.util.ListIterator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.ListIterableSelector; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractOriginalSwapIterator, SubSelection_> extends UpcomingSelectionIterator { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomChangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomChangeIterator.java index f73ba505bf4..2684685c6ad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomChangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomChangeIterator.java @@ -2,9 +2,9 @@ import java.util.Iterator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractRandomChangeIterator> extends UpcomingSelectionIterator { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomSwapIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomSwapIterator.java index 62d7d0a1661..c27d9827748 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomSwapIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/AbstractRandomSwapIterator.java @@ -2,7 +2,7 @@ import java.util.Iterator; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractRandomSwapIterator, SubSelection_> extends UpcomingSelectionIterator { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/CachedListRandomIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/CachedListRandomIterator.java index ff91cf6138a..208bee6fac6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/CachedListRandomIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/CachedListRandomIterator.java @@ -5,7 +5,7 @@ import java.util.NoSuchElementException; import java.util.random.RandomGenerator; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; /** * This {@link Iterator} does not shuffle and is never ending. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/UpcomingSelectionIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/UpcomingSelectionIterator.java index 51c8966b8ed..bd9319b42f6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/UpcomingSelectionIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/iterator/UpcomingSelectionIterator.java @@ -4,11 +4,11 @@ import java.util.NoSuchElementException; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; +import ai.timefold.solver.core.preview.api.move.Move; /** * IMPORTANT: The constructor of any subclass of this abstract class, should never call any of its child diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java index e6ebde368fd..0bcda6e705b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.selector.list; -import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; public record SubList(Object entity, int fromIndex, int length) { @@ -8,8 +8,8 @@ public int getToIndex() { return fromIndex + length; } - public SubList rebase(ScoreDirector destinationScoreDirector) { - return new SubList(destinationScoreDirector.lookUpWorkingObject(entity), fromIndex, length); + public SubList rebase(Rebaser rebaser) { + return new SubList(rebaser.rebase(entity), fromIndex, length); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 1bd9adab8db..6833b01344f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -6,7 +6,6 @@ import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; @@ -19,6 +18,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.SelectedCountLimitMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ShufflingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.SortingMoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractMoveSelectorFactory> extends AbstractSelectorFactory implements MoveSelectorFactory { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java index 1eae3d3ca1c..bdac8d5c7ad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java @@ -1,8 +1,9 @@ package ai.timefold.solver.core.impl.heuristic.selector.move; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; +import ai.timefold.solver.core.preview.api.move.Move; final class DoableMoveSelectionFilter implements SelectionFilter> { @@ -10,7 +11,10 @@ final class DoableMoveSelectionFilter implements SelectionFilter scoreDirector, Move move) { - return move.isMoveDoable(scoreDirector); + if (move instanceof AbstractSelectorBasedMove legacyMove) { + return legacyMove.isMoveDoable(scoreDirector); + } + return true; } private DoableMoveSelectionFilter() { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelector.java index ecce39ca746..ce3ccbe8216 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelector.java @@ -1,7 +1,7 @@ package ai.timefold.solver.core.impl.heuristic.selector.move; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.IterableSelector; +import ai.timefold.solver.core.preview.api.move.Move; /** * Generates {@link Move}s. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/BiasedRandomUnionMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/BiasedRandomUnionMoveIterator.java index 84fb006482e..3950a9b7462 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/BiasedRandomUnionMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/BiasedRandomUnionMoveIterator.java @@ -9,10 +9,10 @@ import java.util.function.ToDoubleFunction; import java.util.random.RandomGenerator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.SelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.random.RandomUtils; +import ai.timefold.solver.core.preview.api.move.Move; final class BiasedRandomUnionMoveIterator extends SelectionIterator> { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelector.java index c883d366e5b..1b5208a8098 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelector.java @@ -6,12 +6,12 @@ import java.util.List; import java.util.NoSuchElementException; -import ai.timefold.solver.core.impl.heuristic.move.CompositeMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedCompositeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.SelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; /** * A {@link CompositeMoveSelector} that Cartesian products 2 or more {@link MoveSelector}s. @@ -24,7 +24,7 @@ */ public class CartesianProductMoveSelector extends CompositeMoveSelector { - private static final Move EMPTY_MARK = NoChangeMove.getInstance(); + private static final Move EMPTY_MARK = SelectorBasedNoChangeMove.getInstance(); private final boolean ignoreEmptyChildIterators; @@ -98,7 +98,7 @@ public OriginalCartesianProductMoveIterator() { protected Move createUpcomingSelection() { var childSize = moveIteratorList.size(); int startingIndex; - Move[] moveList = new Move[childSize]; + var moveList = new Move[childSize]; if (subSelections == null) { startingIndex = -1; } else { @@ -115,7 +115,7 @@ protected Move createUpcomingSelection() { moveIteratorList.set(i, moveIterator); if (!moveIterator.hasNext()) { // in case a moveIterator is empty if (ignoreEmptyChildIterators) { - moveList[i] = (Move) EMPTY_MARK; + moveList[i] = EMPTY_MARK; } else { return noUpcomingSelection(); } @@ -142,7 +142,7 @@ private int findStartingIndex(int childSize) { private Move buildMove(Move[] moveList, int childSize) { if (!ignoreEmptyChildIterators) { - return CompositeMove.buildMove(moveList); + return SelectorBasedCompositeMove.buildMove(moveList); } // Clone because EMPTY_MARK should survive in subSelections Move[] newMoveList = new Move[childSize]; @@ -156,7 +156,7 @@ private Move buildMove(Move[] moveList, int childSize) { return switch (newSize) { case 0 -> noUpcomingSelection(); case 1 -> newMoveList[0]; - default -> CompositeMove.buildMove(Arrays.copyOfRange(newMoveList, 0, newSize)); + default -> SelectorBasedCompositeMove.buildMove(Arrays.copyOfRange(newMoveList, 0, newSize)); }; } @@ -223,7 +223,7 @@ public Move next() { return moveList.get(0); } } - return CompositeMove.buildMove(moveList.toArray(new Move[0])); + return SelectorBasedCompositeMove.buildMove(moveList.toArray(new Move[0])); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UniformRandomUnionMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UniformRandomUnionMoveIterator.java index 95e0c2d6c76..4108de5d35b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UniformRandomUnionMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UniformRandomUnionMoveIterator.java @@ -5,9 +5,9 @@ import java.util.List; import java.util.random.RandomGenerator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.SelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; final class UniformRandomUnionMoveIterator extends SelectionIterator> { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java index 4da87df0718..86139b80950 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java @@ -6,10 +6,10 @@ import java.util.stream.StreamSupport; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; +import ai.timefold.solver.core.preview.api.move.Move; /** * A {@link CompositeMoveSelector} that unions 2 or more {@link MoveSelector}s. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/AbstractCachingMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/AbstractCachingMoveSelector.java index da00309fedd..a6d4117a8ac 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/AbstractCachingMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/AbstractCachingMoveSelector.java @@ -4,12 +4,12 @@ import java.util.List; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public abstract class AbstractCachingMoveSelector extends AbstractMoveSelector implements SelectionCacheLifecycleListener { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelector.java index 0352e93fab6..0b433fc3f62 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelector.java @@ -3,11 +3,11 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.CachingValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; /** * A {@link MoveSelector} that caches the result of its child {@link MoveSelector}. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java index e6b11789b9c..bd9eb98c9d8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java @@ -3,13 +3,13 @@ import java.util.Iterator; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; +import ai.timefold.solver.core.preview.api.move.Move; public final class FilteringMoveSelector extends AbstractMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java index 994a1e98e40..337ad9f041f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java @@ -6,7 +6,6 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; @@ -14,6 +13,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.random.RandomUtils; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public class ProbabilityMoveSelector extends AbstractMoveSelector implements SelectionCacheLifecycleListener { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelector.java index 98c84f70b8e..7039143e440 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelector.java @@ -3,10 +3,10 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.SelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class SelectedCountLimitMoveSelector extends AbstractMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelector.java index 8ff91df07e1..cbdf343b178 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelector.java @@ -3,9 +3,9 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.random.RandomUtils; +import ai.timefold.solver.core.preview.api.move.Move; public class ShufflingMoveSelector extends AbstractCachingMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelector.java index 5124baf6092..b32528e8a31 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelector.java @@ -3,10 +3,10 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public class SortingMoveSelector extends AbstractCachingMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java index 55bc1fd1064..bbe6ef0cf98 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.solver.EnvironmentMode; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; /** * An interface to generate an {@link Iterator} of custom {@link Move}s. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java index 6e8a21debb6..263ddc66715 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java @@ -3,10 +3,10 @@ import java.util.Iterator; import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; +import ai.timefold.solver.core.preview.api.move.Move; /** * Bridges a {@link MoveIteratorFactory} to a {@link MoveSelector}. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactory.java index 8402cc7a0e9..772818167d6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactory.java @@ -5,7 +5,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.preview.api.move.Move; /** * A simple interface to generate a {@link List} of custom {@link Move}s. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryToMoveSelectorBridge.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryToMoveSelectorBridge.java index bd4466ff62c..f536e1b76ee 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryToMoveSelectorBridge.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryToMoveSelectorBridge.java @@ -4,13 +4,13 @@ import java.util.List; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge; import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; /** * Bridges a {@link MoveListFactory} to a {@link MoveSelector}. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelector.java index 7408b1f7770..e2e93ba50fe 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelector.java @@ -3,12 +3,12 @@ import java.util.Iterator; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.IterableSelector; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractOriginalChangeIterator; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractRandomChangeIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class ChangeMoveSelector extends GenericMoveSelector { @@ -60,14 +60,14 @@ public Iterator> iterator() { return new AbstractOriginalChangeIterator<>(entitySelector, valueSelector) { @Override protected Move newChangeSelection(Object entity, Object toValue) { - return new ChangeMove<>(variableDescriptor, entity, toValue); + return new SelectorBasedChangeMove<>(variableDescriptor, entity, toValue); } }; } else { return new AbstractRandomChangeIterator<>(entitySelector, valueSelector) { @Override protected Move newChangeSelection(Object entity, Object toValue) { - return new ChangeMove<>(variableDescriptor, entity, toValue); + return new SelectorBasedChangeMove<>(variableDescriptor, entity, toValue); } }; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java index 45e77a1b982..ac36a918f82 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java @@ -24,14 +24,9 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class ChangeMoveSelectorFactory extends AbstractMoveSelectorFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(ChangeMoveSelectorFactory.class); - public ChangeMoveSelectorFactory(ChangeMoveSelectorConfig moveSelectorConfig) { super(moveSelectorConfig); } @@ -129,12 +124,6 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP private ListChangeMoveSelectorConfig buildListChangeMoveSelectorConfig(ListVariableDescriptor variableDescriptor, boolean inheritFoldedConfig) { - LOGGER.warn( - """ - The changeMoveSelectorConfig ({}) is being used for a list variable. - We are keeping this option through the 1.x release stream for backward compatibility reasons. - Please update your solver config to use {} now.""", - config, ListChangeMoveSelectorConfig.class.getSimpleName()); var listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig(variableDescriptor, config.getValueSelectorConfig(), createDestinationSelectorConfig()); if (inheritFoldedConfig) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelector.java index 1c87918565b..de8eacddc85 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelector.java @@ -4,11 +4,11 @@ import java.util.Iterator; import java.util.List; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.pillar.PillarSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class PillarChangeMoveSelector extends GenericMoveSelector { @@ -76,7 +76,7 @@ protected Move createUpcomingSelection() { } Object toValue = valueIterator.next(); - return new PillarChangeMove<>(upcomingPillar, valueSelector.getVariableDescriptor(), toValue); + return new SelectorBasedPillarChangeMove<>(upcomingPillar, valueSelector.getVariableDescriptor(), toValue); } } @@ -116,7 +116,7 @@ protected Move createUpcomingSelection() { } Object toValue = valueIterator.next(); - return new PillarChangeMove<>(pillar, valueSelector.getVariableDescriptor(), toValue); + return new SelectorBasedPillarChangeMove<>(pillar, valueSelector.getVariableDescriptor(), toValue); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelector.java index 5fc11db2815..343816849a5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelector.java @@ -4,10 +4,10 @@ import java.util.List; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractOriginalSwapIterator; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractRandomSwapIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.pillar.PillarSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class PillarSwapMoveSelector extends GenericMoveSelector { @@ -69,14 +69,14 @@ public Iterator> iterator() { return new AbstractOriginalSwapIterator<>(leftPillarSelector, rightPillarSelector) { @Override protected Move newSwapSelection(List leftSubSelection, List rightSubSelection) { - return new PillarSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); + return new SelectorBasedPillarSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); } }; } else { return new AbstractRandomSwapIterator<>(leftPillarSelector, rightPillarSelector) { @Override protected Move newSwapSelection(List leftSubSelection, List rightSubSelection) { - return new PillarSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); + return new SelectorBasedPillarSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); } }; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveIterator.java index 5b76964cd16..21de92859f1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveIterator.java @@ -2,15 +2,16 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; import java.util.random.RandomGenerator; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; -import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Move; final class RuinRecreateMoveIterator extends UpcomingSelectionIterator> { @@ -40,14 +41,14 @@ protected Move createUpcomingSelection() { var entityIterator = entitySelector.iterator(); var ruinedCount = workingRandom.nextInt(minimumRuinedCount, maximumRuinedCount + 1); var selectedEntityList = new ArrayList<>(ruinedCount); - var affectedValueSet = CollectionUtils.newLinkedHashSet(ruinedCount); - var selectedEntitySet = Collections.newSetFromMap(CollectionUtils.newIdentityHashMap(ruinedCount)); + var affectedValueSet = LinkedHashSet.newLinkedHashSet(ruinedCount); + var selectedEntitySet = Collections.newSetFromMap(new IdentityHashMap<>(ruinedCount)); for (var i = 0; i < ruinedCount; i++) { var remainingAttempts = ruinedCount; while (true) { if (!entityIterator.hasNext()) { // Bail out; cannot select enough unique elements. - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } var selectedEntity = entityIterator.next(); if (selectedEntitySet.add(selectedEntity)) { @@ -62,12 +63,12 @@ protected Move createUpcomingSelection() { } if (remainingAttempts == 0) { // Bail out; cannot select enough unique elements. - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } } } - return new RuinRecreateMove<>(variableDescriptor, constructionHeuristicPhaseBuilder, solverScope, selectedEntityList, - affectedValueSet); + return new SelectorBasedRuinRecreateMove<>(variableDescriptor, constructionHeuristicPhaseBuilder, solverScope, + selectedEntityList, affectedValueSet); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveSelector.java index b4ea28da8ed..98920fe6619 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveSelector.java @@ -3,10 +3,10 @@ import java.util.Iterator; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; import org.apache.commons.math3.util.CombinatoricsUtils; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java similarity index 56% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java index e4a2caacb5c..5cba9b9ff25 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java @@ -1,26 +1,32 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic; -import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class ChangeMove extends AbstractMove { +@NullMarked +public class SelectorBasedChangeMove extends AbstractSelectorBasedMove { protected final GenuineVariableDescriptor variableDescriptor; protected final Object entity; - protected final Object toPlanningValue; + protected final @Nullable Object toPlanningValue; - public ChangeMove(GenuineVariableDescriptor variableDescriptor, Object entity, Object toPlanningValue) { + public SelectorBasedChangeMove(GenuineVariableDescriptor variableDescriptor, Object entity, + @Nullable Object toPlanningValue) { this.variableDescriptor = variableDescriptor; this.entity = entity; this.toPlanningValue = toPlanningValue; @@ -34,7 +40,7 @@ public Object getEntity() { return entity; } - public Object getToPlanningValue() { + public @Nullable Object getToPlanningValue() { return toPlanningValue; } @@ -44,38 +50,34 @@ public Object getToPlanningValue() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - Object oldValue = variableDescriptor.getValue(entity); + var oldValue = variableDescriptor.getValue(entity); return !Objects.equals(oldValue, toPlanningValue); } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - castScoreDirector.changeVariableFacade(variableDescriptor, entity, toPlanningValue); + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + scoreDirector.changeVariableFacade(variableDescriptor, entity, toPlanningValue); } @Override - public ChangeMove rebase(ScoreDirector destinationScoreDirector) { - return new ChangeMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(entity), - destinationScoreDirector.lookUpWorkingObject(toPlanningValue)); + public SelectorBasedChangeMove rebase(Rebaser rebaser) { + return new SelectorBasedChangeMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(entity)), + rebaser.rebase(toPlanningValue)); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "ChangeMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return Collections.singletonList(entity); } @Override - public Collection getPlanningValues() { + public SequencedCollection<@Nullable Object> getPlanningValues() { return Collections.singletonList(toPlanningValue); } @@ -87,7 +89,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - final ChangeMove other = (ChangeMove) o; + var other = (SelectorBasedChangeMove) o; return Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(entity, other.entity) && Objects.equals(toPlanningValue, other.toPlanningValue); @@ -100,8 +102,9 @@ public int hashCode() { @Override public String toString() { - Object oldValue = variableDescriptor.getValue(entity); - return entity + " {" + oldValue + " -> " + toPlanningValue + "}"; + var oldValue = variableDescriptor.getValue(entity); + return "%s {%s -> %s}" + .formatted(entity, oldValue, toPlanningValue); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java similarity index 59% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java index cc191f9e53b..040a16aa3ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java @@ -1,31 +1,36 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * This {@link Move} is not cacheable. * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class PillarChangeMove extends AbstractMove { +@NullMarked +public class SelectorBasedPillarChangeMove extends AbstractSelectorBasedMove { protected final GenuineVariableDescriptor variableDescriptor; protected final List pillar; - protected final Object toPlanningValue; + protected final @Nullable Object toPlanningValue; - public PillarChangeMove(List pillar, GenuineVariableDescriptor variableDescriptor, - Object toPlanningValue) { + public SelectorBasedPillarChangeMove(List pillar, GenuineVariableDescriptor variableDescriptor, + @Nullable Object toPlanningValue) { this.pillar = pillar; this.variableDescriptor = variableDescriptor; this.toPlanningValue = toPlanningValue; @@ -39,7 +44,7 @@ public String getVariableName() { return variableDescriptor.getVariableName(); } - public Object getToPlanningValue() { + public @Nullable Object getToPlanningValue() { return toPlanningValue; } @@ -49,13 +54,13 @@ public Object getToPlanningValue() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - var oldValue = variableDescriptor.getValue(pillar.get(0)); + var oldValue = variableDescriptor.getValue(pillar.getFirst()); if (Objects.equals(oldValue, toPlanningValue)) { return false; } if (!variableDescriptor.canExtractValueRangeFromSolution()) { var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor(); - for (Object entity : pillar) { + for (var entity : pillar) { var rightValueRange = extractValueRangeFromEntity(scoreDirector, valueRangeDescriptor, entity); if (!rightValueRange.contains(toPlanningValue)) { return false; @@ -66,35 +71,31 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { for (var entity : pillar) { - castScoreDirector.changeVariableFacade(variableDescriptor, entity, toPlanningValue); + scoreDirector.changeVariableFacade(variableDescriptor, entity, toPlanningValue); } } @Override - public PillarChangeMove rebase(ScoreDirector destinationScoreDirector) { - return new PillarChangeMove<>(rebaseList(pillar, destinationScoreDirector), variableDescriptor, - destinationScoreDirector.lookUpWorkingObject(toPlanningValue)); + public SelectorBasedPillarChangeMove rebase(Rebaser rebaser) { + return new SelectorBasedPillarChangeMove<>(rebaseList(pillar, rebaser), variableDescriptor, + rebaser.rebase(toPlanningValue)); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "PillarChangeMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return pillar; } @Override - public Collection getPlanningValues() { + public SequencedCollection<@Nullable Object> getPlanningValues() { return Collections.singletonList(toPlanningValue); } @@ -106,7 +107,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - final PillarChangeMove other = (PillarChangeMove) o; + var other = (SelectorBasedPillarChangeMove) o; return Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(pillar, other.pillar) && Objects.equals(toPlanningValue, other.toPlanningValue); @@ -119,8 +120,9 @@ public int hashCode() { @Override public String toString() { - Object oldValue = variableDescriptor.getValue(pillar.get(0)); - return pillar + " {" + oldValue + " -> " + toPlanningValue + "}"; + var oldValue = variableDescriptor.getValue(pillar.getFirst()); + return "%s {%s -> %s}" + .formatted(pillar, oldValue, toPlanningValue); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java similarity index 65% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java index 9dd8e7572f4..3c82bad5843 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java @@ -1,31 +1,35 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * This {@link Move} is not cacheable. * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class PillarSwapMove extends AbstractMove { +@NullMarked +public class SelectorBasedPillarSwapMove extends AbstractSelectorBasedMove { protected final List> variableDescriptorList; protected final List leftPillar; protected final List rightPillar; - public PillarSwapMove(List> variableDescriptorList, + public SelectorBasedPillarSwapMove(List> variableDescriptorList, List leftPillar, List rightPillar) { this.variableDescriptorList = variableDescriptorList; this.leftPillar = leftPillar; @@ -34,7 +38,7 @@ public PillarSwapMove(List> variableDescrip public List getVariableNameList() { List variableNameList = new ArrayList<>(variableDescriptorList.size()); - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + for (var variableDescriptor : variableDescriptorList) { variableNameList.add(variableDescriptor.getVariableName()); } return variableNameList; @@ -56,8 +60,8 @@ public List getRightPillar() { public boolean isMoveDoable(ScoreDirector scoreDirector) { var movable = false; for (var variableDescriptor : variableDescriptorList) { - var leftValue = variableDescriptor.getValue(leftPillar.get(0)); - var rightValue = variableDescriptor.getValue(rightPillar.get(0)); + var leftValue = variableDescriptor.getValue(leftPillar.getFirst()); + var rightValue = variableDescriptor.getValue(rightPillar.getFirst()); if (!Objects.equals(leftValue, rightValue)) { movable = true; if (!variableDescriptor.canExtractValueRangeFromSolution()) { @@ -83,38 +87,33 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { - Object oldLeftValue = variableDescriptor.getValue(leftPillar.get(0)); - Object oldRightValue = variableDescriptor.getValue(rightPillar.get(0)); + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + for (var variableDescriptor : variableDescriptorList) { + var oldLeftValue = variableDescriptor.getValue(leftPillar.getFirst()); + var oldRightValue = variableDescriptor.getValue(rightPillar.getFirst()); if (!Objects.equals(oldLeftValue, oldRightValue)) { - for (Object leftEntity : leftPillar) { - castScoreDirector.changeVariableFacade(variableDescriptor, leftEntity, oldRightValue); + for (var leftEntity : leftPillar) { + scoreDirector.changeVariableFacade(variableDescriptor, leftEntity, oldRightValue); } - for (Object rightEntity : rightPillar) { - castScoreDirector.changeVariableFacade(variableDescriptor, rightEntity, oldLeftValue); + for (var rightEntity : rightPillar) { + scoreDirector.changeVariableFacade(variableDescriptor, rightEntity, oldLeftValue); } } } } @Override - public PillarSwapMove rebase(ScoreDirector destinationScoreDirector) { - return new PillarSwapMove<>(variableDescriptorList, - rebaseList(leftPillar, destinationScoreDirector), rebaseList(rightPillar, destinationScoreDirector)); + public SelectorBasedPillarSwapMove rebase(Rebaser rebaser) { + return new SelectorBasedPillarSwapMove<>(variableDescriptorList, rebaseList(leftPillar, rebaser), + rebaseList(rightPillar, rebaser)); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - StringBuilder moveTypeDescription = new StringBuilder(20 * (variableDescriptorList.size() + 1)); - moveTypeDescription.append(getClass().getSimpleName()).append("("); - String delimiter = ""; - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + public String describe() { + var moveTypeDescription = new StringBuilder(20 * (variableDescriptorList.size() + 1)); + moveTypeDescription.append("PillarSwapMove("); + var delimiter = ""; + for (var variableDescriptor : variableDescriptorList) { moveTypeDescription.append(delimiter).append(variableDescriptor.getSimpleEntityAndVariableName()); delimiter = ", "; } @@ -123,16 +122,16 @@ public String getSimpleMoveTypeDescription() { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return CollectionUtils.concat(leftPillar, rightPillar); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { List values = new ArrayList<>(variableDescriptorList.size() * 2); - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { - values.add(variableDescriptor.getValue(leftPillar.get(0))); - values.add(variableDescriptor.getValue(rightPillar.get(0))); + for (var variableDescriptor : variableDescriptorList) { + values.add(variableDescriptor.getValue(leftPillar.getFirst())); + values.add(variableDescriptor.getValue(rightPillar.getFirst())); } return values; } @@ -145,7 +144,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - final PillarSwapMove other = (PillarSwapMove) o; + final var other = (SelectorBasedPillarSwapMove) o; return Objects.equals(variableDescriptorList, other.variableDescriptorList) && Objects.equals(leftPillar, other.leftPillar) && Objects.equals(rightPillar, other.rightPillar); @@ -158,7 +157,7 @@ public int hashCode() { @Override public String toString() { - StringBuilder s = new StringBuilder(variableDescriptorList.size() * 16); + var s = new StringBuilder(variableDescriptorList.size() * 16); s.append(leftPillar).append(" {"); appendVariablesToString(s, leftPillar); s.append("} <-> "); @@ -169,12 +168,12 @@ public String toString() { } protected void appendVariablesToString(StringBuilder s, List pillar) { - boolean first = true; - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + var first = true; + for (var variableDescriptor : variableDescriptorList) { if (!first) { s.append(", "); } - var value = variableDescriptor.getValue(pillar.get(0)); + var value = variableDescriptor.getValue(pillar.getFirst()); s.append(value == null ? null : value.toString()); first = false; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java similarity index 61% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java index fca24ecd73c..58ff2bb3e46 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java @@ -1,31 +1,37 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; +import java.util.SequencedSet; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; -public final class RuinRecreateMove extends AbstractMove { +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public final class SelectorBasedRuinRecreateMove extends AbstractSelectorBasedMove { private final GenuineVariableDescriptor genuineVariableDescriptor; private final RuinRecreateConstructionHeuristicPhaseBuilder constructionHeuristicPhaseBuilder; private final SolverScope solverScope; private final List ruinedEntityList; - private final Set affectedValueSet; + private final SequencedSet affectedValueSet; - private Object[] recordedNewValues; + private Object @Nullable [] recordedNewValues; - public RuinRecreateMove(GenuineVariableDescriptor genuineVariableDescriptor, + public SelectorBasedRuinRecreateMove(GenuineVariableDescriptor genuineVariableDescriptor, RuinRecreateConstructionHeuristicPhaseBuilder constructionHeuristicPhaseBuilder, - SolverScope solverScope, List ruinedEntityList, Set affectedValueSet) { + SolverScope solverScope, List ruinedEntityList, SequencedSet affectedValueSet) { this.genuineVariableDescriptor = genuineVariableDescriptor; this.ruinedEntityList = ruinedEntityList; this.affectedValueSet = affectedValueSet; @@ -35,26 +41,30 @@ public RuinRecreateMove(GenuineVariableDescriptor genuineVariableDesc } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { recordedNewValues = new Object[ruinedEntityList.size()]; - var recordingScoreDirector = (VariableChangeRecordingScoreDirector) scoreDirector; for (var ruinedEntity : ruinedEntityList) { - recordingScoreDirector.beforeVariableChanged(genuineVariableDescriptor, ruinedEntity); + scoreDirector.beforeVariableChanged(genuineVariableDescriptor, ruinedEntity); genuineVariableDescriptor.setValue(ruinedEntity, null); - recordingScoreDirector.afterVariableChanged(genuineVariableDescriptor, ruinedEntity); + scoreDirector.afterVariableChanged(genuineVariableDescriptor, ruinedEntity); } - recordingScoreDirector.triggerVariableListeners(); + scoreDirector.triggerVariableListeners(); + var backingScoreDirector = + (scoreDirector instanceof VariableChangeRecordingScoreDirector recordingScoreDirector) + ? recordingScoreDirector.getBacking() + : scoreDirector; + var innerScoreDirector = (InnerScoreDirector) backingScoreDirector; var constructionHeuristicPhase = (RuinRecreateConstructionHeuristicPhase) constructionHeuristicPhaseBuilder - .ensureThreadSafe(recordingScoreDirector.getBacking()) + .ensureThreadSafe(innerScoreDirector) .withElementsToRecreate(ruinedEntityList) .build(); var nestedSolverScope = new SolverScope(solverScope.getClock()); nestedSolverScope.setSolver(solverScope.getSolver()); - nestedSolverScope.setScoreDirector(recordingScoreDirector.getBacking()); + nestedSolverScope.setScoreDirector(innerScoreDirector); constructionHeuristicPhase.solvingStarted(nestedSolverScope); constructionHeuristicPhase.solve(nestedSolverScope); constructionHeuristicPhase.solvingEnded(nestedSolverScope); @@ -66,25 +76,20 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return ruinedEntityList; } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return affectedValueSet; } @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return true; - } - - @Override - public Move rebase(ScoreDirector destinationScoreDirector) { - var rebasedRuinedEntityList = rebaseList(ruinedEntityList, destinationScoreDirector); - var rebasedAffectedValueSet = rebaseSet(affectedValueSet, destinationScoreDirector); - return new RuinRecreateMove<>(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope, + public Move rebase(Rebaser rebaser) { + var rebasedRuinedEntityList = rebaseList(ruinedEntityList, rebaser); + var rebasedAffectedValueSet = rebaseSet(affectedValueSet, rebaser); + return new SelectorBasedRuinRecreateMove<>(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope, rebasedRuinedEntityList, rebasedAffectedValueSet); } @@ -92,7 +97,7 @@ public Move rebase(ScoreDirector destinationScoreDirector) public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof RuinRecreateMove that)) + if (!(o instanceof SelectorBasedRuinRecreateMove that)) return false; return Objects.equals(genuineVariableDescriptor, that.genuineVariableDescriptor) && Objects.equals(ruinedEntityList, that.ruinedEntityList) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java index eb913efc6aa..985b9aff6b0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java @@ -1,28 +1,31 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class SwapMove extends AbstractMove { +@NullMarked +public class SelectorBasedSwapMove extends AbstractSelectorBasedMove { protected final List> variableDescriptorList; protected final Object leftEntity; protected final Object rightEntity; - public SwapMove(List> variableDescriptorList, Object leftEntity, + public SelectorBasedSwapMove(List> variableDescriptorList, Object leftEntity, Object rightEntity) { this.variableDescriptorList = variableDescriptorList; this.leftEntity = leftEntity; @@ -57,35 +60,29 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - public SwapMove rebase(ScoreDirector destinationScoreDirector) { - return new SwapMove<>(variableDescriptorList, - destinationScoreDirector.lookUpWorkingObject(leftEntity), - destinationScoreDirector.lookUpWorkingObject(rightEntity)); - } - - @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { for (var variableDescriptor : variableDescriptorList) { var oldLeftValue = variableDescriptor.getValue(leftEntity); var oldRightValue = variableDescriptor.getValue(rightEntity); if (!Objects.equals(oldLeftValue, oldRightValue)) { - castScoreDirector.changeVariableFacade(variableDescriptor, leftEntity, oldRightValue); - castScoreDirector.changeVariableFacade(variableDescriptor, rightEntity, oldLeftValue); + scoreDirector.changeVariableFacade(variableDescriptor, leftEntity, oldRightValue); + scoreDirector.changeVariableFacade(variableDescriptor, rightEntity, oldLeftValue); } } } - // ************************************************************************ - // Introspection methods - // ************************************************************************ + @Override + public SelectorBasedSwapMove rebase(Rebaser rebaser) { + return new SelectorBasedSwapMove<>(variableDescriptorList, Objects.requireNonNull(rebaser.rebase(leftEntity)), + Objects.requireNonNull(rebaser.rebase(rightEntity))); + } @Override - public String getSimpleMoveTypeDescription() { - StringBuilder moveTypeDescription = new StringBuilder(20 * (variableDescriptorList.size() + 1)); - moveTypeDescription.append(getClass().getSimpleName()).append("("); - String delimiter = ""; - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + public String describe() { + var moveTypeDescription = new StringBuilder(20 * (variableDescriptorList.size() + 1)); + moveTypeDescription.append("SwapMove("); + var delimiter = ""; + for (var variableDescriptor : variableDescriptorList) { moveTypeDescription.append(delimiter).append(variableDescriptor.getSimpleEntityAndVariableName()); delimiter = ", "; } @@ -94,14 +91,14 @@ public String getSimpleMoveTypeDescription() { } @Override - public Collection getPlanningEntities() { - return Arrays.asList(leftEntity, rightEntity); + public SequencedCollection getPlanningEntities() { + return List.of(leftEntity, rightEntity); } @Override - public Collection getPlanningValues() { - List values = new ArrayList<>(variableDescriptorList.size() * 2); - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + public SequencedCollection getPlanningValues() { + var values = new ArrayList<>(variableDescriptorList.size() * 2); + for (var variableDescriptor : variableDescriptorList) { values.add(variableDescriptor.getValue(leftEntity)); values.add(variableDescriptor.getValue(rightEntity)); } @@ -116,7 +113,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - final SwapMove swapMove = (SwapMove) o; + var swapMove = (SelectorBasedSwapMove) o; return Objects.equals(variableDescriptorList, swapMove.variableDescriptorList) && Objects.equals(leftEntity, swapMove.leftEntity) && Objects.equals(rightEntity, swapMove.rightEntity); @@ -129,7 +126,7 @@ public int hashCode() { @Override public String toString() { - StringBuilder s = new StringBuilder(variableDescriptorList.size() * 16); + var s = new StringBuilder(variableDescriptorList.size() * 16); s.append(leftEntity).append(" {"); appendVariablesToString(s, leftEntity); s.append("} <-> "); @@ -140,8 +137,8 @@ public String toString() { } protected void appendVariablesToString(StringBuilder s, Object entity) { - boolean first = true; - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + var first = true; + for (var variableDescriptor : variableDescriptorList) { if (!first) { s.append(", "); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelector.java index 4e9c67dd4b9..b0ed24ba8e7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelector.java @@ -5,10 +5,10 @@ import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractOriginalSwapIterator; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractRandomSwapIterator; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; +import ai.timefold.solver.core.preview.api.move.Move; public class SwapMoveSelector extends GenericMoveSelector { @@ -76,14 +76,14 @@ public Iterator> iterator() { return new AbstractOriginalSwapIterator<>(leftEntitySelector, rightEntitySelector) { @Override protected Move newSwapSelection(Object leftSubSelection, Object rightSubSelection) { - return new SwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); + return new SelectorBasedSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); } }; } else { return new AbstractRandomSwapIterator<>(leftEntitySelector, rightEntitySelector) { @Override protected Move newSwapSelection(Object leftSubSelection, Object rightSubSelection) { - return new SwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); + return new SelectorBasedSwapMove<>(variableDescriptorList, leftSubSelection, rightSubSelection); } }; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java index c9277ecb315..0c5f12c3805 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java @@ -21,14 +21,9 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class SwapMoveSelectorFactory extends AbstractMoveSelectorFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(SwapMoveSelectorFactory.class); - public SwapMoveSelectorFactory(SwapMoveSelectorConfig moveSelectorConfig) { // We copy the configuration, // as the settings may be updated during the autoconfiguration of the entity value range @@ -170,12 +165,6 @@ EntitySelectorFactory. create(config.getSecondaryEntitySelectorConfig private ListSwapMoveSelectorConfig buildListSwapMoveSelectorConfig(VariableDescriptor variableDescriptor, boolean inheritFoldedConfig) { - LOGGER.warn( - """ - The swapMoveSelectorConfig ({}) is being used for a list variable. - We are keeping this option through the 1.x release stream for backward compatibility reasons. - Please update your solver config to use {} now.""", - config, ListSwapMoveSelectorConfig.class.getSimpleName()); var listSwapMoveSelectorConfig = new ListSwapMoveSelectorConfig(); var childValueSelectorConfig = new ValueSelectorConfig( new ValueSelectorConfig(variableDescriptor.getVariableName())); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelector.java index c9b374ba6ca..02549522ab7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelector.java @@ -6,13 +6,13 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement; +import ai.timefold.solver.core.preview.api.move.Move; public class ListChangeMoveSelector extends GenericMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelector.java index a80a1d6809f..d1413ca29d9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelector.java @@ -7,10 +7,10 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public class ListSwapMoveSelector extends GenericMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListChangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListChangeIterator.java index 89e7d9ff1ef..854042be90e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListChangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListChangeIterator.java @@ -5,13 +5,13 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; +import ai.timefold.solver.core.preview.api.move.Move; /** * @@ -62,18 +62,19 @@ static Move buildChangeMove( var upcomingSource = listVariableStateSupply.getElementPosition(upcomingLeftValue); if (upcomingSource instanceof PositionInList sourceElement) { if (upcomingDestination instanceof PositionInList destinationElement) { - return new ListChangeMove<>(listVariableDescriptor, sourceElement.entity(), sourceElement.index(), + return new SelectorBasedListChangeMove<>(listVariableDescriptor, sourceElement.entity(), sourceElement.index(), destinationElement.entity(), destinationElement.index()); } else { - return new ListUnassignMove<>(listVariableDescriptor, sourceElement.entity(), sourceElement.index()); + return new SelectorBasedListUnassignMove<>(listVariableDescriptor, sourceElement.entity(), + sourceElement.index()); } } else { if (upcomingDestination instanceof PositionInList destinationElement) { - return new ListAssignMove<>(listVariableDescriptor, upcomingLeftValue, destinationElement.entity(), + return new SelectorBasedListAssignMove<>(listVariableDescriptor, upcomingLeftValue, destinationElement.entity(), destinationElement.index()); } else { // Only used in construction heuristics to give the CH an option to leave the element unassigned. - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListSwapIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListSwapIterator.java index 7d6062db71b..8ed3f74794d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListSwapIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/OriginalListSwapIterator.java @@ -5,12 +5,12 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; -import ai.timefold.solver.core.impl.heuristic.move.CompositeMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedCompositeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement; +import ai.timefold.solver.core.preview.api.move.Move; /** * @@ -49,7 +49,7 @@ protected Move createUpcomingSelection() { static Move buildSwapMove(ListVariableStateSupply listVariableStateSupply, Object upcomingLeftValue, Object upcomingRightValue) { if (upcomingLeftValue == upcomingRightValue) { - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } var listVariableDescriptor = listVariableStateSupply.getSourceVariableDescriptor(); var upcomingLeft = listVariableStateSupply.getElementPosition(upcomingLeftValue); @@ -57,25 +57,29 @@ static Move buildSwapMove(ListVariableStateSupply(listVariableDescriptor, rightDestination.entity(), rightDestination.index()); - var assignMove = new ListAssignMove<>(listVariableDescriptor, upcomingLeftValue, rightDestination.entity(), - rightDestination.index()); - return CompositeMove.buildMove(unassignMove, assignMove); + new SelectorBasedListUnassignMove<>(listVariableDescriptor, rightDestination.entity(), + rightDestination.index()); + var assignMove = + new SelectorBasedListAssignMove<>(listVariableDescriptor, upcomingLeftValue, rightDestination.entity(), + rightDestination.index()); + return SelectorBasedCompositeMove.buildMove(unassignMove, assignMove); } else if (rightUnassigned) { // Unassign left, put right where left used to be. var leftDestination = upcomingLeft.ensureAssigned(); var unassignMove = - new ListUnassignMove<>(listVariableDescriptor, leftDestination.entity(), leftDestination.index()); - var assignMove = new ListAssignMove<>(listVariableDescriptor, upcomingRightValue, leftDestination.entity(), - leftDestination.index()); - return CompositeMove.buildMove(unassignMove, assignMove); + new SelectorBasedListUnassignMove<>(listVariableDescriptor, leftDestination.entity(), + leftDestination.index()); + var assignMove = + new SelectorBasedListAssignMove<>(listVariableDescriptor, upcomingRightValue, leftDestination.entity(), + leftDestination.index()); + return SelectorBasedCompositeMove.buildMove(unassignMove, assignMove); } else { var leftDestination = upcomingLeft.ensureAssigned(); var rightDestination = upcomingRight.ensureAssigned(); - return new ListSwapMove<>(listVariableDescriptor, leftDestination.entity(), leftDestination.index(), + return new SelectorBasedListSwapMove<>(listVariableDescriptor, leftDestination.entity(), leftDestination.index(), rightDestination.entity(), rightDestination.index()); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListChangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListChangeIterator.java index 0881fd551df..6f3f6cdd911 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListChangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListChangeIterator.java @@ -4,11 +4,11 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; +import ai.timefold.solver.core.preview.api.move.Move; /** * @param the solution type, the class with the {@link PlanningSolution} annotation diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListSwapIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListSwapIterator.java index 98494055b2f..bd33a450584 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListSwapIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomListSwapIterator.java @@ -6,9 +6,9 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; /** * @param the solution type, the class with the {@link PlanningSolution} annotation diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveIterator.java index ccffbdcb3ad..cf530875851 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveIterator.java @@ -4,13 +4,13 @@ import java.util.random.RandomGenerator; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; +import ai.timefold.solver.core.preview.api.move.Move; class RandomSubListChangeMoveIterator extends UpcomingSelectionIterator> { @@ -47,11 +47,11 @@ protected Move createUpcomingSelection() { return noUpcomingSelection(); } else if (destination instanceof PositionInList destinationElement) { var reversing = selectReversingMoveToo && workingRandom.nextBoolean(); - return new SubListChangeMove<>(listVariableDescriptor, subList, destinationElement.entity(), + return new SelectorBasedSubListChangeMove<>(listVariableDescriptor, subList, destinationElement.entity(), destinationElement.index(), reversing); } else { // TODO add SubListAssignMove - return new SubListUnassignMove<>(listVariableDescriptor, subList); + return new SelectorBasedSubListUnassignMove<>(listVariableDescriptor, subList); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveSelector.java index bcb52890173..02985b411d2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveSelector.java @@ -2,10 +2,10 @@ import java.util.Iterator; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class RandomSubListChangeMoveSelector extends GenericMoveSelector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListSwapMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListSwapMoveSelector.java index 7108d92f6d7..b202d282462 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListSwapMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListSwapMoveSelector.java @@ -3,11 +3,11 @@ import java.util.Iterator; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.AbstractRandomSwapIterator; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; +import ai.timefold.solver.core.preview.api.move.Move; public class RandomSubListSwapMoveSelector extends GenericMoveSelector { @@ -42,7 +42,8 @@ public Iterator> iterator() { @Override protected Move newSwapSelection(SubList leftSubSelection, SubList rightSubSelection) { boolean reversing = selectReversingMoveToo && workingRandom.nextBoolean(); - return new SubListSwapMove<>(listVariableDescriptor, leftSubSelection, rightSubSelection, reversing); + return new SelectorBasedSubListSwapMove<>(listVariableDescriptor, leftSubSelection, rightSubSelection, + reversing); } }; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java similarity index 58% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java index 35ebd0dc2cd..58e80f995ae 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java @@ -1,22 +1,27 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; -public final class ListAssignMove extends AbstractMove { +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class SelectorBasedListAssignMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object planningValue; private final Object destinationEntity; private final int destinationIndex; - public ListAssignMove(ListVariableDescriptor variableDescriptor, Object planningValue, Object destinationEntity, + public SelectorBasedListAssignMove(ListVariableDescriptor variableDescriptor, Object planningValue, + Object destinationEntity, int destinationIndex) { this.variableDescriptor = variableDescriptor; this.planningValue = planningValue; @@ -37,12 +42,12 @@ public Object getMovedValue() { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(destinationEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return List.of(planningValue); } @@ -52,27 +57,25 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var variableDescriptorAwareScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { // Add planningValue to destinationEntity's list variable (at destinationIndex). - variableDescriptorAwareScoreDirector.beforeListVariableElementAssigned(variableDescriptor, planningValue); - variableDescriptorAwareScoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, - destinationIndex); + scoreDirector.beforeListVariableElementAssigned(variableDescriptor, planningValue); + scoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex); variableDescriptor.addElement(destinationEntity, destinationIndex, planningValue); - variableDescriptorAwareScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, - destinationIndex + 1); - variableDescriptorAwareScoreDirector.afterListVariableElementAssigned(variableDescriptor, planningValue); + scoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex + 1); + scoreDirector.afterListVariableElementAssigned(variableDescriptor, planningValue); } @Override - public ListAssignMove rebase(ScoreDirector destinationScoreDirector) { - return new ListAssignMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(planningValue), - destinationScoreDirector.lookUpWorkingObject(destinationEntity), destinationIndex); + public SelectorBasedListAssignMove rebase(Rebaser rebaser) { + return new SelectorBasedListAssignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(planningValue)), + Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex); } @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "ListAssignMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override @@ -83,7 +86,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ListAssignMove other = (ListAssignMove) o; + var other = (SelectorBasedListAssignMove) o; return destinationIndex == other.destinationIndex && Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(planningValue, other.planningValue) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java similarity index 65% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java index 3f2e9fbac93..0192cf87b78 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java @@ -1,17 +1,20 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Moves an element of a {@link PlanningListVariable list variable}. The moved element is identified @@ -22,7 +25,8 @@ * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class ListChangeMove extends AbstractMove { +@NullMarked +public class SelectorBasedListChangeMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object sourceEntity; @@ -30,7 +34,7 @@ public class ListChangeMove extends AbstractMove { private final Object destinationEntity; private final int destinationIndex; - private Object planningValue; + private @Nullable Object planningValue; /** * The move removes a planning value element from {@code sourceEntity.listVariable[sourceIndex]} @@ -88,7 +92,8 @@ public class ListChangeMove extends AbstractMove { * @param destinationEntity planning entity instance to which a planning value will be moved, for example "Bob" * @param destinationIndex index in destinationEntity's list variable where the moved planning value will be inserted */ - public ListChangeMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, int sourceIndex, + public SelectorBasedListChangeMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, + int sourceIndex, Object destinationEntity, int destinationIndex) { this.variableDescriptor = variableDescriptor; this.sourceEntity = sourceEntity; @@ -126,67 +131,57 @@ public Object getMovedValue() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - // TODO maybe remove this because no such move should be generated - // Do not use Object#equals on user-provided domain objects. Relying on user's implementation of Object#equals - // opens the opportunity to shoot themselves in the foot if different entities can be equal. - var sameEntity = destinationEntity == sourceEntity; - return !sameEntity + return !Objects.equals(sourceEntity, destinationEntity) || (destinationIndex != sourceIndex && destinationIndex != variableDescriptor.getListSize(sourceEntity)); } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - - if (sourceEntity == destinationEntity) { - int fromIndex = Math.min(sourceIndex, destinationIndex); - int toIndex = Math.max(sourceIndex, destinationIndex) + 1; - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); - Object element = variableDescriptor.removeElement(sourceEntity, sourceIndex); + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + if (Objects.equals(sourceEntity, destinationEntity)) { + var fromIndex = Math.min(sourceIndex, destinationIndex); + var toIndex = Math.max(sourceIndex, destinationIndex) + 1; + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); + var element = variableDescriptor.removeElement(sourceEntity, sourceIndex); variableDescriptor.addElement(destinationEntity, destinationIndex, element); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); planningValue = element; } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1); - Object element = variableDescriptor.removeElement(sourceEntity, sourceIndex); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1); + var element = variableDescriptor.removeElement(sourceEntity, sourceIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); - castScoreDirector.beforeListVariableChanged(variableDescriptor, - destinationEntity, destinationIndex, destinationIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex); variableDescriptor.addElement(destinationEntity, destinationIndex, element); - castScoreDirector.afterListVariableChanged(variableDescriptor, - destinationEntity, destinationIndex, destinationIndex + 1); + scoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, + destinationIndex + 1); planningValue = element; } } @Override - public ListChangeMove rebase(ScoreDirector destinationScoreDirector) { - return new ListChangeMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(sourceEntity), sourceIndex, - destinationScoreDirector.lookUpWorkingObject(destinationEntity), destinationIndex); + public SelectorBasedListChangeMove rebase(Rebaser rebaser) { + return new SelectorBasedListChangeMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)), + sourceIndex, Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "ListChangeMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { // Use LinkedHashSet for predictable iteration order. - Set entities = new LinkedHashSet<>(2); + var entities = LinkedHashSet.newLinkedHashSet(2); entities.add(sourceEntity); entities.add(destinationEntity); return entities; } @Override - public Collection getPlanningValues() { - return Collections.singleton(planningValue); + public SequencedCollection<@Nullable Object> getPlanningValues() { + return Collections.singletonList(planningValue); } @Override @@ -197,7 +192,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ListChangeMove other = (ListChangeMove) o; + var other = (SelectorBasedListChangeMove) o; return sourceIndex == other.sourceIndex && destinationIndex == other.destinationIndex && Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(sourceEntity, other.sourceEntity) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java similarity index 69% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java index 3839b0c47e3..47a9ad0b456 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java @@ -1,17 +1,19 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Arrays; -import java.util.Collection; import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * Swaps two elements of a {@link PlanningListVariable list variable}. @@ -24,7 +26,8 @@ * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class ListSwapMove extends AbstractMove { +@NullMarked +public class SelectorBasedListSwapMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object leftEntity; @@ -73,7 +76,7 @@ public class ListSwapMove extends AbstractMove { * @param rightEntity together with {@code rightIndex} identifies the right element to be moved * @param rightIndex together with {@code rightEntity} identifies the right element to be moved */ - public ListSwapMove(ListVariableDescriptor variableDescriptor, Object leftEntity, int leftIndex, + public SelectorBasedListSwapMove(ListVariableDescriptor variableDescriptor, Object leftEntity, int leftIndex, Object rightEntity, int rightIndex) { this.variableDescriptor = variableDescriptor; this.leftEntity = leftEntity; @@ -112,62 +115,55 @@ public Object getRightValue() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - // Do not use Object#equals on user-provided domain objects. Relying on user's implementation of Object#equals - // opens the opportunity to shoot themselves in the foot if different entities can be equal. - var sameEntity = leftEntity == rightEntity; - return !(sameEntity && leftIndex == rightIndex); + return !(Objects.equals(leftEntity, rightEntity) && leftIndex == rightIndex); } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var leftElement = variableDescriptor.getElement(leftEntity, leftIndex); var rightElement = variableDescriptor.getElement(rightEntity, rightIndex); - if (leftEntity == rightEntity) { + if (Objects.equals(leftEntity, rightEntity)) { var fromIndex = Math.min(leftIndex, rightIndex); var toIndex = Math.max(leftIndex, rightIndex) + 1; - castScoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); variableDescriptor.setElement(leftEntity, leftIndex, rightElement); variableDescriptor.setElement(rightEntity, rightIndex, leftElement); - castScoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, leftIndex, leftIndex + 1); - castScoreDirector.beforeListVariableChanged(variableDescriptor, rightEntity, rightIndex, rightIndex + 1); + scoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, leftIndex, leftIndex + 1); + scoreDirector.beforeListVariableChanged(variableDescriptor, rightEntity, rightIndex, rightIndex + 1); variableDescriptor.setElement(leftEntity, leftIndex, rightElement); variableDescriptor.setElement(rightEntity, rightIndex, leftElement); - castScoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, leftIndex, leftIndex + 1); - castScoreDirector.afterListVariableChanged(variableDescriptor, rightEntity, rightIndex, rightIndex + 1); + scoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, leftIndex, leftIndex + 1); + scoreDirector.afterListVariableChanged(variableDescriptor, rightEntity, rightIndex, rightIndex + 1); } } @Override - public ListSwapMove rebase(ScoreDirector destinationScoreDirector) { - return new ListSwapMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(leftEntity), leftIndex, - destinationScoreDirector.lookUpWorkingObject(rightEntity), rightIndex); + public SelectorBasedListSwapMove rebase(Rebaser rebaser) { + return new SelectorBasedListSwapMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(leftEntity)), + leftIndex, Objects.requireNonNull(rebaser.rebase(rightEntity)), rightIndex); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "ListSwapMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { // Use LinkedHashSet for predictable iteration order. - Set entities = new LinkedHashSet<>(2); + var entities = LinkedHashSet.newLinkedHashSet(2); entities.add(leftEntity); entities.add(rightEntity); return entities; } @Override - public Collection getPlanningValues() { - return Arrays.asList(getLeftValue(), getRightValue()); + public SequencedCollection getPlanningValues() { + return List.of(getLeftValue(), getRightValue()); } @Override @@ -178,7 +174,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ListSwapMove other = (ListSwapMove) o; + var other = (SelectorBasedListSwapMove) o; return leftIndex == other.leftIndex && rightIndex == other.rightIndex && Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(leftEntity, other.leftEntity) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java similarity index 58% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java index 71de762af06..1cbe82e20b2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java @@ -1,24 +1,29 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; -public class ListUnassignMove extends AbstractMove { +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class SelectorBasedListUnassignMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object sourceEntity; private final int sourceIndex; - private Object movedValue; + private @Nullable Object movedValue; - public ListUnassignMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, int sourceIndex) { + public SelectorBasedListUnassignMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, + int sourceIndex) { this.variableDescriptor = variableDescriptor; this.sourceEntity = sourceEntity; this.sourceIndex = sourceIndex; @@ -40,12 +45,12 @@ public Object getMovedValue() { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(sourceEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return List.of(getMovedValue()); } @@ -55,32 +60,32 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var listVariable = variableDescriptor.getValue(sourceEntity); var element = getMovedValue(); // Remove an element from sourceEntity's list variable (at sourceIndex). - castScoreDirector.beforeListVariableElementUnassigned(variableDescriptor, element); - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1); + scoreDirector.beforeListVariableElementUnassigned(variableDescriptor, element); + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1); movedValue = listVariable.remove(sourceIndex); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); - castScoreDirector.afterListVariableElementUnassigned(variableDescriptor, element); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); + scoreDirector.afterListVariableElementUnassigned(variableDescriptor, element); } @Override - public Move rebase(ScoreDirector destinationScoreDirector) { - return new ListUnassignMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(sourceEntity), + public SelectorBasedListUnassignMove rebase(Rebaser rebaser) { + return new SelectorBasedListUnassignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex); } @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "ListUnassignMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override public boolean equals(Object o) { - if (o instanceof ListUnassignMove other) { + if (o instanceof SelectorBasedListUnassignMove other) { return sourceIndex == other.sourceIndex && Objects.equals(variableDescriptor, other.variableDescriptor) && Objects.equals(sourceEntity, other.sourceEntity); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java similarity index 68% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java index aa6de2f4b57..2cbb60b7521 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java @@ -1,24 +1,27 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.LinkedHashSet; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; -import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class SubListChangeMove extends AbstractMove { +@NullMarked +public class SelectorBasedSubListChangeMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object sourceEntity; @@ -28,15 +31,16 @@ public class SubListChangeMove extends AbstractMove { private final int destinationIndex; private final boolean reversing; - private Collection planningValues; + private @Nullable SequencedCollection planningValues; - public SubListChangeMove(ListVariableDescriptor variableDescriptor, SubList subList, Object destinationEntity, + public SelectorBasedSubListChangeMove(ListVariableDescriptor variableDescriptor, SubList subList, + Object destinationEntity, int destinationIndex, boolean reversing) { this(variableDescriptor, subList.entity(), subList.fromIndex(), subList.length(), destinationEntity, destinationIndex, reversing); } - public SubListChangeMove( + public SelectorBasedSubListChangeMove( ListVariableDescriptor variableDescriptor, Object sourceEntity, int sourceIndex, int length, Object destinationEntity, int destinationIndex, boolean reversing) { @@ -79,7 +83,7 @@ public int getDestinationIndex() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { - var sameEntity = destinationEntity == sourceEntity; + var sameEntity = Objects.equals(sourceEntity, destinationEntity); if (sameEntity && destinationIndex == sourceIndex) { return false; } @@ -90,7 +94,7 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { // When the first and second elements are different, // and the value range is located at the entity, // we need to check if the destination's value range accepts the upcoming values - ValueRangeManager valueRangeManager = + var valueRangeManager = ((VariableDescriptorAwareScoreDirector) scoreDirector).getValueRangeManager(); var destinationValueRange = valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), @@ -101,61 +105,53 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var sourceList = variableDescriptor.getValue(sourceEntity); var subList = sourceList.subList(sourceIndex, sourceIndex + length); planningValues = CollectionUtils.copy(subList, reversing); - if (sourceEntity == destinationEntity) { + if (Objects.equals(sourceEntity, destinationEntity)) { var fromIndex = Math.min(sourceIndex, destinationIndex); var toIndex = Math.max(sourceIndex, destinationIndex) + length; - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); subList.clear(); variableDescriptor.getValue(destinationEntity).addAll(destinationIndex, planningValues); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, fromIndex, toIndex); } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + length); + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + length); subList.clear(); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); - castScoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, - destinationIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex); variableDescriptor.getValue(destinationEntity).addAll(destinationIndex, planningValues); - castScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, + scoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex + length); } } @Override - public SubListChangeMove rebase(ScoreDirector destinationScoreDirector) { - return new SubListChangeMove<>( - variableDescriptor, - destinationScoreDirector.lookUpWorkingObject(sourceEntity), sourceIndex, length, - destinationScoreDirector.lookUpWorkingObject(destinationEntity), destinationIndex, - reversing); + public SelectorBasedSubListChangeMove rebase(Rebaser rebaser) { + return new SelectorBasedSubListChangeMove<>(variableDescriptor, + Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex, length, + Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex, reversing); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "SubListChangeMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { // Use LinkedHashSet for predictable iteration order. - Set entities = new LinkedHashSet<>(2); + var entities = LinkedHashSet.newLinkedHashSet(2); entities.add(sourceEntity); entities.add(destinationEntity); return entities; } @Override - public Collection getPlanningValues() { + public SequencedCollection<@Nullable Object> getPlanningValues() { return planningValues; } @@ -167,7 +163,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - SubListChangeMove other = (SubListChangeMove) o; + var other = (SelectorBasedSubListChangeMove) o; return sourceIndex == other.sourceIndex && length == other.length && destinationIndex == other.destinationIndex && reversing == other.reversing && variableDescriptor.equals(other.variableDescriptor) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java similarity index 60% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java index 6ddd97d1962..5ab60c04d69 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java @@ -1,25 +1,28 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; -import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class SubListSwapMove extends AbstractMove { +@NullMarked +public class SelectorBasedSubListSwapMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final SubList leftSubList; @@ -28,23 +31,17 @@ public class SubListSwapMove extends AbstractMove { private final int rightFromIndex; private final int leftToIndex; - private List leftPlanningValueList; - private List rightPlanningValueList; + private @Nullable List leftPlanningValueList; + private @Nullable List rightPlanningValueList; - public SubListSwapMove(ListVariableDescriptor variableDescriptor, - Object leftEntity, int leftFromIndex, int leftToIndex, - Object rightEntity, int rightFromIndex, int rightToIndex, - boolean reversing) { - this(variableDescriptor, - new SubList(leftEntity, leftFromIndex, leftToIndex - leftFromIndex), - new SubList(rightEntity, rightFromIndex, rightToIndex - rightFromIndex), - reversing); + public SelectorBasedSubListSwapMove(ListVariableDescriptor variableDescriptor, Object leftEntity, + int leftFromIndex, int leftToIndex, Object rightEntity, int rightFromIndex, int rightToIndex, boolean reversing) { + this(variableDescriptor, new SubList(leftEntity, leftFromIndex, leftToIndex - leftFromIndex), + new SubList(rightEntity, rightFromIndex, rightToIndex - rightFromIndex), reversing); } - public SubListSwapMove(ListVariableDescriptor variableDescriptor, - SubList leftSubList, - SubList rightSubList, - boolean reversing) { + public SelectorBasedSubListSwapMove(ListVariableDescriptor variableDescriptor, SubList leftSubList, + SubList rightSubList, boolean reversing) { this.variableDescriptor = variableDescriptor; if (leftSubList.entity() == rightSubList.entity() && leftSubList.fromIndex() > rightSubList.fromIndex()) { this.leftSubList = rightSubList; @@ -80,24 +77,19 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { // When the left and right elements are different, // and the value range is located at the entity, // we need to check if the destination's value range accepts the upcoming values - ValueRangeManager valueRangeManager = + var valueRangeManager = ((VariableDescriptorAwareScoreDirector) scoreDirector).getValueRangeManager(); var leftEntity = leftSubList.entity(); var leftList = subList(leftSubList); - var leftValueRange = - valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), leftEntity); + var leftValueRange = valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), leftEntity); var rightEntity = rightSubList.entity(); var rightList = subList(rightSubList); - var rightValueRange = - valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), rightEntity); - return leftList.stream().allMatch(rightValueRange::contains) - && rightList.stream().allMatch(leftValueRange::contains); + var rightValueRange = valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), rightEntity); + return leftList.stream().allMatch(rightValueRange::contains) && rightList.stream().allMatch(leftValueRange::contains); } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var leftEntity = leftSubList.entity(); var rightEntity = rightSubList.entity(); var leftSubListLength = leftSubList.length(); @@ -112,61 +104,53 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) if (leftEntity == rightEntity) { var fromIndex = Math.min(leftFromIndex, rightFromIndex); - var toIndex = leftFromIndex > rightFromIndex - ? leftFromIndex + leftSubListLength - : rightFromIndex + rightSubListLength; + var toIndex = + leftFromIndex > rightFromIndex ? leftFromIndex + leftSubListLength : rightFromIndex + rightSubListLength; var leftSubListDestinationIndex = rightFromIndex + rightSubListLength - leftSubListLength; - castScoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); + scoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); rightSubListView.clear(); subList(leftSubList).clear(); leftList.addAll(leftFromIndex, rightPlanningValueList); rightList.addAll(leftSubListDestinationIndex, leftPlanningValueList); - castScoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, fromIndex, toIndex); } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, - leftEntity, leftFromIndex, leftFromIndex + leftSubListLength); - castScoreDirector.beforeListVariableChanged(variableDescriptor, - rightEntity, rightFromIndex, rightFromIndex + rightSubListLength); + scoreDirector.beforeListVariableChanged(variableDescriptor, leftEntity, leftFromIndex, + leftFromIndex + leftSubListLength); + scoreDirector.beforeListVariableChanged(variableDescriptor, rightEntity, rightFromIndex, + rightFromIndex + rightSubListLength); rightSubListView.clear(); leftSubListView.clear(); leftList.addAll(leftFromIndex, rightPlanningValueList); rightList.addAll(rightFromIndex, leftPlanningValueList); - castScoreDirector.afterListVariableChanged(variableDescriptor, - leftEntity, leftFromIndex, leftFromIndex + rightSubListLength); - castScoreDirector.afterListVariableChanged(variableDescriptor, - rightEntity, rightFromIndex, rightFromIndex + leftSubListLength); + scoreDirector.afterListVariableChanged(variableDescriptor, leftEntity, leftFromIndex, + leftFromIndex + rightSubListLength); + scoreDirector.afterListVariableChanged(variableDescriptor, rightEntity, rightFromIndex, + rightFromIndex + leftSubListLength); } } @Override - public SubListSwapMove rebase(ScoreDirector destinationScoreDirector) { - return new SubListSwapMove<>( - variableDescriptor, - leftSubList.rebase(destinationScoreDirector), - rightSubList.rebase(destinationScoreDirector), + public SelectorBasedSubListSwapMove rebase(Rebaser rebaser) { + return new SelectorBasedSubListSwapMove<>(variableDescriptor, leftSubList.rebase(rebaser), rightSubList.rebase(rebaser), reversing); } - // ************************************************************************ - // Introspection methods - // ************************************************************************ - @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "SubListSwapMove(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { // Use LinkedHashSet for predictable iteration order. - Set entities = new LinkedHashSet<>(2); + var entities = LinkedHashSet.newLinkedHashSet(2); entities.add(leftSubList.entity()); entities.add(rightSubList.entity()); return entities; } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return CollectionUtils.concat(leftPlanningValueList, rightPlanningValueList); } @@ -182,10 +166,9 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - SubListSwapMove other = (SubListSwapMove) o; + var other = (SelectorBasedSubListSwapMove) o; return reversing == other.reversing && rightFromIndex == other.rightFromIndex && leftToIndex == other.leftToIndex - && variableDescriptor.equals(other.variableDescriptor) - && leftSubList.equals(other.leftSubList) + && variableDescriptor.equals(other.variableDescriptor) && leftSubList.equals(other.leftSubList) && rightSubList.equals(other.rightSubList); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java similarity index 59% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListUnassignMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java index 96a1104b05d..de072505cd2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListUnassignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java @@ -1,35 +1,41 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public class SubListUnassignMove extends AbstractMove { +@NullMarked +public class SelectorBasedSubListUnassignMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object sourceEntity; private final int sourceIndex; private final int length; - private Collection planningValues; + private @Nullable SequencedCollection planningValues; - public SubListUnassignMove(ListVariableDescriptor variableDescriptor, SubList subList) { + public SelectorBasedSubListUnassignMove(ListVariableDescriptor variableDescriptor, SubList subList) { this(variableDescriptor, subList.entity(), subList.fromIndex(), subList.length()); } - private SubListUnassignMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, int sourceIndex, - int length) { + private SelectorBasedSubListUnassignMove(ListVariableDescriptor variableDescriptor, Object sourceEntity, + int sourceIndex, int length) { this.variableDescriptor = variableDescriptor; this.sourceEntity = sourceEntity; this.sourceIndex = sourceIndex; @@ -61,46 +67,45 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var sourceList = variableDescriptor.getValue(sourceEntity); var subList = sourceList.subList(sourceIndex, getToIndex()); planningValues = List.copyOf(subList); for (var element : subList) { - castScoreDirector.beforeListVariableElementUnassigned(variableDescriptor, element); - castScoreDirector.afterListVariableElementUnassigned(variableDescriptor, element); + scoreDirector.beforeListVariableElementUnassigned(variableDescriptor, element); + scoreDirector.afterListVariableElementUnassigned(variableDescriptor, element); } - castScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, getToIndex()); + scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, getToIndex()); subList.clear(); - castScoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); + scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); } @Override - public SubListUnassignMove rebase(ScoreDirector destinationScoreDirector) { - return new SubListUnassignMove<>(variableDescriptor, destinationScoreDirector.lookUpWorkingObject(sourceEntity), + public Move rebase(Rebaser rebaser) { + return new SelectorBasedSubListUnassignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex, length); } @Override - public String getSimpleMoveTypeDescription() { - return getClass().getSimpleName() + "(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; + public String describe() { + return "SubListUnassignMove(%s)" + .formatted(variableDescriptor.getSimpleEntityAndVariableName()); } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(sourceEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return planningValues; } @Override public boolean equals(Object o) { - if (o instanceof SubListUnassignMove other) { + if (o instanceof SelectorBasedSubListUnassignMove other) { return sourceIndex == other.sourceIndex && length == other.length && variableDescriptor.equals(other.variableDescriptor) && sourceEntity.equals(other.sourceEntity); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/FlipSublistAction.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/FlipSublistAction.java index 7bde455a7c4..07d27f0ee90 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/FlipSublistAction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/FlipSublistAction.java @@ -6,7 +6,8 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; /** - * Flips a sublist of a list variable, (the same thing as a {@link TwoOptListMove}, but no shift to restore the original + * Flips a sublist of a list variable, (the same thing as a {@link SelectorBasedTwoOptListMove}, but no shift to restore the + * original * origin). * For instance, given [0, 1, 2, 3, 4], fromIndexInclusive = 1, toIndexExclusive = 3, * the list after the move would be [0, 3, 2, 1, 4]. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java index b9eb61e797f..f4f228e57e1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java @@ -121,7 +121,7 @@ static int[] computeInEdgesForSequentialMove(Node_[] removedEdges) { // **************************************************** /** - * This return a {@link KOptListMove} that corresponds to this {@link KOptDescriptor}.
+ * This returns a {@link SelectorBasedKOptListMove} that corresponds to this {@link KOptDescriptor}.
*
* It implements the algorithm described in the paper * "Transforming Cabbage into Turnip: Polynomial @@ -129,7 +129,7 @@ static int[] computeInEdgesForSequentialMove(Node_[] removedEdges) { * "An Effective Implementation of K-opt Moves * for the Lin-Kernighan TSP Heuristic" (Section 5.4 "Execution of a feasible move") to perform a K-opt move * by performing the minimal number of list reversals to transform the current route into the new route after the - * K-opt. We use it here to calculate the {@link FlipSublistAction} list for the {@link KOptListMove} that is + * K-opt. We use it here to calculate the {@link FlipSublistAction} list for the {@link SelectorBasedKOptListMove} that is * described by this {@link KOptDescriptor}.
*
* The algorithm goal is to convert a signed permutation (p_1, p_2, ..., p_(2k)) into the identify permutation @@ -157,12 +157,13 @@ static int[] computeInEdgesForSequentialMove(Node_[] removedEdges) { * * */ - public KOptListMove + public SelectorBasedKOptListMove getKOptListMove(ListVariableStateSupply listVariableStateSupply) { var listVariableDescriptor = listVariableStateSupply.getSourceVariableDescriptor(); if (!isFeasible()) { // A KOptListMove move with an empty flip move list is not feasible, since if executed, it's a no-op. - return new KOptListMove<>(listVariableDescriptor, this, new MultipleDelegateList<>(), List.of(), 0, new int[] {}); + return new SelectorBasedKOptListMove<>(listVariableDescriptor, this, new MultipleDelegateList<>(), List.of(), 0, + new int[] {}); } var combinedList = computeCombinedList(listVariableDescriptor, listVariableStateSupply); @@ -274,7 +275,8 @@ static int[] computeInEdgesForSequentialMove(Node_[] removedEdges) { .toArray(); newEndIndices[newEndIndices.length - 1] = originalToCurrentIndexList.length - 1; - return new KOptListMove<>(listVariableDescriptor, this, combinedList, out, startElementShift, newEndIndices); + return new SelectorBasedKOptListMove<>(listVariableDescriptor, this, combinedList, out, startElementShift, + newEndIndices); } /** diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIterator.java index 91c4c6edc77..7a1df8beba8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIterator.java @@ -6,10 +6,10 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; +import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -61,7 +61,7 @@ protected Move createUpcomingSelection() { var descriptor = pickKOptMove(k); if (descriptor == null) { // Was unable to find a K-Opt move - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } return descriptor.getKOptListMove(listVariableStateSupply); } @@ -70,21 +70,22 @@ private Move pickTwoOptMove() { @SuppressWarnings("unchecked") var originIterator = (Iterator) originSelector.iterator(); if (!originIterator.hasNext()) { - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } // The inner node may need the outer iterator to select the next value first Object firstValue = originIterator.next(); @SuppressWarnings("unchecked") var valueIterator = (Iterator) valueSelector.iterator(); if (!valueIterator.hasNext()) { - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } Object secondValue = valueIterator.next(); var firstElementPosition = listVariableStateSupply.getElementPosition(firstValue) .ensureAssigned(); var secondElementPosition = listVariableStateSupply.getElementPosition(secondValue) .ensureAssigned(); - return new TwoOptListMove<>(listVariableDescriptor, firstElementPosition.entity(), secondElementPosition.entity(), + return new SelectorBasedTwoOptListMove<>(listVariableDescriptor, firstElementPosition.entity(), + secondElementPosition.entity(), firstElementPosition.index(), secondElementPosition.index()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelector.java index a3e5e090a87..921193d2909 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelector.java @@ -8,11 +8,11 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; import org.apache.commons.math3.util.CombinatoricsUtils; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java similarity index 76% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java index 6d58098e4c1..6a2c928ec17 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java @@ -1,21 +1,25 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.kopt; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * @param the solution type, the class with the {@link PlanningSolution} annotation */ -public final class KOptListMove extends AbstractMove { +@NullMarked +public final class SelectorBasedKOptListMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor listVariableDescriptor; private final KOptDescriptor descriptor; @@ -25,11 +29,8 @@ public final class KOptListMove extends AbstractMove { private final int[] newEndIndices; private final Object[] originalEntities; - KOptListMove(ListVariableDescriptor listVariableDescriptor, - KOptDescriptor descriptor, - MultipleDelegateList combinedList, - List equivalent2Opts, - int postShiftAmount, + SelectorBasedKOptListMove(ListVariableDescriptor listVariableDescriptor, KOptDescriptor descriptor, + MultipleDelegateList combinedList, List equivalent2Opts, int postShiftAmount, int[] newEndIndices) { this.listVariableDescriptor = listVariableDescriptor; this.descriptor = descriptor; @@ -41,7 +42,7 @@ public final class KOptListMove extends AbstractMove { } else if (postShiftAmount != 0) { affectedElementsInfo = KOptAffectedElements.forMiddleRange(0, combinedList.size()); } else { - var currentAffectedElements = equivalent2Opts.get(0).getAffectedElements(); + var currentAffectedElements = equivalent2Opts.getFirst().getAffectedElements(); for (var i = 1; i < equivalent2Opts.size(); i++) { currentAffectedElements = currentAffectedElements.merge(equivalent2Opts.get(i).getAffectedElements()); } @@ -51,12 +52,8 @@ public final class KOptListMove extends AbstractMove { originalEntities = combinedList.delegateEntities; } - private KOptListMove(ListVariableDescriptor listVariableDescriptor, - KOptDescriptor descriptor, - List equivalent2Opts, - int postShiftAmount, - int[] newEndIndices, - Object[] originalEntities) { + private SelectorBasedKOptListMove(ListVariableDescriptor listVariableDescriptor, KOptDescriptor descriptor, + List equivalent2Opts, int postShiftAmount, int[] newEndIndices, Object[] originalEntities) { this.listVariableDescriptor = listVariableDescriptor; this.descriptor = descriptor; this.equivalent2Opts = equivalent2Opts; @@ -68,7 +65,7 @@ private KOptListMove(ListVariableDescriptor listVariableDescriptor, affectedElementsInfo = KOptAffectedElements.forMiddleRange(0, computeCombinedList(listVariableDescriptor, originalEntities).size()); } else { - var currentAffectedElements = equivalent2Opts.get(0).getAffectedElements(); + var currentAffectedElements = equivalent2Opts.getFirst().getAffectedElements(); for (var i = 1; i < equivalent2Opts.size(); i++) { currentAffectedElements = currentAffectedElements.merge(equivalent2Opts.get(i).getAffectedElements()); } @@ -83,15 +80,10 @@ KOptDescriptor getDescriptor() { } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var castScoreDirector = (VariableDescriptorAwareScoreDirector) scoreDirector; - + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { var combinedList = computeCombinedList(listVariableDescriptor, originalEntities); - combinedList.actOnAffectedElements(listVariableDescriptor, - originalEntities, - (entity, start, end) -> castScoreDirector.beforeListVariableChanged(listVariableDescriptor, entity, - start, - end)); + combinedList.actOnAffectedElements(listVariableDescriptor, originalEntities, + (entity, start, end) -> scoreDirector.beforeListVariableChanged(listVariableDescriptor, entity, start, end)); // subLists will get corrupted by ConcurrentModifications, so do the operations // on a clone @@ -100,11 +92,8 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) // At this point, all related genuine variables are actually updated combinedList.applyChangesFromCopy(combinedListCopy); - combinedList.actOnAffectedElements(listVariableDescriptor, - originalEntities, - (entity, start, end) -> castScoreDirector.afterListVariableChanged(listVariableDescriptor, entity, - start, - end)); + combinedList.actOnAffectedElements(listVariableDescriptor, originalEntities, + (entity, start, end) -> scoreDirector.afterListVariableChanged(listVariableDescriptor, entity, start, end)); } @Override @@ -130,34 +119,33 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - public KOptListMove rebase(ScoreDirector destinationScoreDirector) { + public SelectorBasedKOptListMove rebase(Rebaser rebaser) { var rebasedEquivalent2Opts = new ArrayList(equivalent2Opts.size()); - var castScoreDirector = (VariableDescriptorAwareScoreDirector) destinationScoreDirector; var newEntities = new Object[originalEntities.length]; for (var i = 0; i < newEntities.length; i++) { - newEntities[i] = castScoreDirector.lookUpWorkingObject(originalEntities[i]); + newEntities[i] = rebaser.rebase(originalEntities[i]); } for (var twoOpt : equivalent2Opts) { rebasedEquivalent2Opts.add(twoOpt.rebase()); } - return new KOptListMove<>(listVariableDescriptor, descriptor, rebasedEquivalent2Opts, postShiftAmount, newEndIndices, - newEntities); + return new SelectorBasedKOptListMove<>(listVariableDescriptor, descriptor, rebasedEquivalent2Opts, postShiftAmount, + newEndIndices, newEntities); } @Override - public String getSimpleMoveTypeDescription() { + public String describe() { return descriptor.k() + "-opt(" + listVariableDescriptor.getSimpleEntityAndVariableName() + ")"; } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(originalEntities); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { var out = new ArrayList<>(); var combinedList = computeCombinedList(listVariableDescriptor, originalEntities); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java similarity index 78% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java index d6d102b90ce..03ac5b01dbd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java @@ -1,16 +1,20 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.kopt; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Set; +import java.util.List; +import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NullMarked; /** * A 2-opt move for list variables, which takes two edges and swap their endpoints. @@ -44,7 +48,8 @@ * * @param */ -public final class TwoOptListMove extends AbstractMove { +@NullMarked +public final class SelectorBasedTwoOptListMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor variableDescriptor; private final Object firstEntity; private final Object secondEntity; @@ -54,10 +59,8 @@ public final class TwoOptListMove extends AbstractMove { private final int shift; private final int entityFirstUnpinnedIndex; - public TwoOptListMove(ListVariableDescriptor variableDescriptor, - Object firstEntity, Object secondEntity, - int firstEdgeEndpoint, - int secondEdgeEndpoint) { + public SelectorBasedTwoOptListMove(ListVariableDescriptor variableDescriptor, Object firstEntity, + Object secondEntity, int firstEdgeEndpoint, int secondEdgeEndpoint) { this.variableDescriptor = variableDescriptor; this.firstEntity = firstEntity; this.secondEntity = secondEntity; @@ -84,12 +87,8 @@ public TwoOptListMove(ListVariableDescriptor variableDescriptor, } } - private TwoOptListMove(ListVariableDescriptor variableDescriptor, - Object firstEntity, Object secondEntity, - int firstEdgeEndpoint, - int secondEdgeEndpoint, - int entityFirstUnpinnedIndex, - int shift) { + private SelectorBasedTwoOptListMove(ListVariableDescriptor variableDescriptor, Object firstEntity, + Object secondEntity, int firstEdgeEndpoint, int secondEdgeEndpoint, int entityFirstUnpinnedIndex, int shift) { this.variableDescriptor = variableDescriptor; this.firstEntity = firstEntity; this.secondEntity = secondEntity; @@ -100,7 +99,7 @@ private TwoOptListMove(ListVariableDescriptor variableDescriptor, } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { if (firstEntity == secondEntity) { doSublistReversal(scoreDirector); } else { @@ -115,12 +114,8 @@ private void doTailSwap(ScoreDirector scoreDirector) { var firstOriginalSize = firstListVariable.size(); var secondOriginalSize = secondListVariable.size(); - castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, - firstEdgeEndpoint, - firstOriginalSize); - castScoreDirector.beforeListVariableChanged(variableDescriptor, secondEntity, - secondEdgeEndpoint, - secondOriginalSize); + castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, firstEdgeEndpoint, firstOriginalSize); + castScoreDirector.beforeListVariableChanged(variableDescriptor, secondEntity, secondEdgeEndpoint, secondOriginalSize); var firstListVariableTail = firstListVariable.subList(firstEdgeEndpoint, firstOriginalSize); var secondListVariableTail = secondListVariable.subList(secondEdgeEndpoint, secondOriginalSize); @@ -133,11 +128,9 @@ private void doTailSwap(ScoreDirector scoreDirector) { secondListVariableTail.clear(); secondListVariable.addAll(firstListVariableTailCopy); - castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, - firstEdgeEndpoint, + castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, firstEdgeEndpoint, firstOriginalSize + tailSizeDifference); - castScoreDirector.afterListVariableChanged(variableDescriptor, secondEntity, - secondEdgeEndpoint, + castScoreDirector.afterListVariableChanged(variableDescriptor, secondEntity, secondEdgeEndpoint, secondOriginalSize - tailSizeDifference); } @@ -147,12 +140,10 @@ private void doSublistReversal(ScoreDirector scoreDirector) { if (firstEdgeEndpoint < secondEdgeEndpoint) { if (firstEdgeEndpoint > 0) { - castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, - firstEdgeEndpoint, + castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, firstEdgeEndpoint, secondEdgeEndpoint); } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, - entityFirstUnpinnedIndex, + castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, entityFirstUnpinnedIndex, listVariable.size()); } @@ -160,8 +151,7 @@ private void doSublistReversal(ScoreDirector scoreDirector) { if (entityFirstUnpinnedIndex == 0) { Collections.rotate(listVariable, shift); } else { - Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), - shift); + Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), shift); } } @@ -171,31 +161,26 @@ private void doSublistReversal(ScoreDirector scoreDirector) { if (entityFirstUnpinnedIndex == 0) { Collections.rotate(listVariable, shift); } else { - Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), - shift); + Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), shift); } } if (firstEdgeEndpoint > 0) { - castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, - firstEdgeEndpoint, + castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, firstEdgeEndpoint, secondEdgeEndpoint); } else { - castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, - entityFirstUnpinnedIndex, + castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, entityFirstUnpinnedIndex, listVariable.size()); } } else { - castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, - entityFirstUnpinnedIndex, + castScoreDirector.beforeListVariableChanged(variableDescriptor, firstEntity, entityFirstUnpinnedIndex, listVariable.size()); if (shift > 0) { if (entityFirstUnpinnedIndex == 0) { Collections.rotate(listVariable, shift); } else { - Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), - shift); + Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), shift); } } @@ -205,12 +190,10 @@ private void doSublistReversal(ScoreDirector scoreDirector) { if (entityFirstUnpinnedIndex == 0) { Collections.rotate(listVariable, shift); } else { - Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), - shift); + Collections.rotate(listVariable.subList(entityFirstUnpinnedIndex, listVariable.size()), shift); } } - castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, - entityFirstUnpinnedIndex, + castScoreDirector.afterListVariableChanged(variableDescriptor, firstEntity, entityFirstUnpinnedIndex, listVariable.size()); } } @@ -231,12 +214,10 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { // we need to check if the destination's value range accepts the upcoming values ValueRangeManager valueRangeManager = ((VariableDescriptorAwareScoreDirector) scoreDirector).getValueRangeManager(); - var firstValueRange = - valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), firstEntity); + var firstValueRange = valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), firstEntity); var firstListVariable = variableDescriptor.getValue(firstEntity); var firstListVariableTail = firstListVariable.subList(firstEdgeEndpoint, firstListVariable.size()); - var secondValueRange = - valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), secondEntity); + var secondValueRange = valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(), secondEntity); var secondListVariable = variableDescriptor.getValue(secondEntity); var secondListVariableTail = secondListVariable.subList(secondEdgeEndpoint, secondListVariable.size()); return firstListVariableTail.stream().allMatch(secondValueRange::contains) @@ -244,31 +225,27 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) { } @Override - public TwoOptListMove rebase(ScoreDirector destinationScoreDirector) { - return new TwoOptListMove<>(variableDescriptor, - destinationScoreDirector.lookUpWorkingObject(firstEntity), - destinationScoreDirector.lookUpWorkingObject(secondEntity), - firstEdgeEndpoint, - secondEdgeEndpoint, - entityFirstUnpinnedIndex, - shift); + public SelectorBasedTwoOptListMove rebase(Rebaser rebaser) { + return new SelectorBasedTwoOptListMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(firstEntity)), + Objects.requireNonNull(rebaser.rebase(secondEntity)), firstEdgeEndpoint, secondEdgeEndpoint, + entityFirstUnpinnedIndex, shift); } @Override - public String getSimpleMoveTypeDescription() { + public String describe() { return "2-Opt(" + variableDescriptor.getSimpleEntityAndVariableName() + ")"; } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { if (firstEntity == secondEntity) { - return Collections.singleton(firstEntity); + return List.of(firstEntity); } - return Set.of(firstEntity, secondEntity); + return List.of(firstEntity, secondEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { if (firstEntity == secondEntity) { var listVariable = variableDescriptor.getValue(firstEntity); if (firstEdgeEndpoint < secondEdgeEndpoint) { @@ -308,11 +285,7 @@ public Object getSecondEdgeEndpoint() { @Override public String toString() { - return "2-Opt(firstEntity=" + - firstEntity + - ", secondEntity=" + secondEntity + - ", firstEndpointIndex=" + firstEdgeEndpoint + - ", secondEndpointIndex=" + secondEdgeEndpoint + - ")"; + return "2-Opt(firstEntity=" + firstEntity + ", secondEntity=" + secondEntity + ", firstEndpointIndex=" + + firstEdgeEndpoint + ", secondEndpointIndex=" + secondEdgeEndpoint + ")"; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveIterator.java index fabc8f7d1bb..90591a3a22f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveIterator.java @@ -2,16 +2,17 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; import java.util.random.RandomGenerator; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder; import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; -import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Move; final class ListRuinRecreateMoveIterator extends UpcomingSelectionIterator> { @@ -43,14 +44,14 @@ protected Move createUpcomingSelection() { var valueIterator = valueSelector.iterator(); var ruinedCount = workingRandom.nextInt(minimumRuinedCount, maximumRuinedCount + 1); var selectedValueList = new ArrayList<>(ruinedCount); - var affectedEntitySet = CollectionUtils.newLinkedHashSet(ruinedCount); - var selectedValueSet = Collections.newSetFromMap(CollectionUtils.newIdentityHashMap(ruinedCount)); + var affectedEntitySet = LinkedHashSet.newLinkedHashSet(ruinedCount); + var selectedValueSet = Collections.newSetFromMap(new IdentityHashMap<>(ruinedCount)); for (var i = 0; i < ruinedCount; i++) { var remainingAttempts = ruinedCount; while (true) { if (!valueIterator.hasNext()) { // Bail out; cannot select enough unique elements. - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } var selectedValue = valueIterator.next(); if (selectedValueSet.add(selectedValue)) { @@ -65,11 +66,11 @@ protected Move createUpcomingSelection() { } if (remainingAttempts == 0) { // Bail out; cannot select enough unique elements. - return NoChangeMove.getInstance(); + return SelectorBasedNoChangeMove.getInstance(); } } } - return new ListRuinRecreateMove<>(listVariableStateSupply.getSourceVariableDescriptor(), + return new SelectorBasedListRuinRecreateMove<>(listVariableStateSupply.getSourceVariableDescriptor(), constructionHeuristicPhaseBuilder, solverScope, selectedValueList, affectedEntitySet); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelector.java index a04d4257e1e..d1709cdfb62 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelector.java @@ -5,7 +5,6 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.CountSupplier; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.GenericMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder; @@ -13,6 +12,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; import org.apache.commons.math3.util.CombinatoricsUtils; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java similarity index 74% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java index e02edbe02bb..b3e1c49da52 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java @@ -1,53 +1,62 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin; import java.util.ArrayList; -import java.util.Collection; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Objects; -import java.util.Set; +import java.util.SequencedCollection; +import java.util.SequencedSet; import java.util.TreeSet; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.AbstractMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhase; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder; +import ai.timefold.solver.core.impl.move.MoveDirector; import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; -import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.Rebaser; -public final class ListRuinRecreateMove extends AbstractMove { +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class SelectorBasedListRuinRecreateMove extends AbstractSelectorBasedMove { private final ListVariableDescriptor listVariableDescriptor; private final List ruinedValueList; - private final Set affectedEntitySet; + private final SequencedSet affectedEntitySet; private final RuinRecreateConstructionHeuristicPhaseBuilder constructionHeuristicPhaseBuilder; private final SolverScope solverScope; private final Map> entityToNewPositionMap; - public ListRuinRecreateMove(ListVariableDescriptor listVariableDescriptor, + public SelectorBasedListRuinRecreateMove(ListVariableDescriptor listVariableDescriptor, RuinRecreateConstructionHeuristicPhaseBuilder constructionHeuristicPhaseBuilder, - SolverScope solverScope, List ruinedValueList, Set affectedEntitySet) { + SolverScope solverScope, List ruinedValueList, SequencedSet affectedEntitySet) { this.listVariableDescriptor = listVariableDescriptor; this.constructionHeuristicPhaseBuilder = constructionHeuristicPhaseBuilder; this.solverScope = solverScope; this.ruinedValueList = ruinedValueList; this.affectedEntitySet = affectedEntitySet; - this.entityToNewPositionMap = CollectionUtils.newIdentityHashMap(affectedEntitySet.size()); + this.entityToNewPositionMap = new IdentityHashMap<>(affectedEntitySet.size()); } @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { entityToNewPositionMap.clear(); - var variableChangeRecordingScoreDirector = (VariableChangeRecordingScoreDirector) scoreDirector; - try (var listVariableStateSupply = variableChangeRecordingScoreDirector.getBacking().getSupplyManager() + var variableChangeRecordingScoreDirector = + scoreDirector instanceof VariableChangeRecordingScoreDirector recordingScoreDirector + ? recordingScoreDirector + : new VariableChangeRecordingScoreDirector<>(scoreDirector); + var nonRecordingScoreDirector = variableChangeRecordingScoreDirector.getBacking(); + var onlyRecordingChangesScoreDirector = variableChangeRecordingScoreDirector.getNonDelegating(); + try (var listVariableStateSupply = nonRecordingScoreDirector.getSupplyManager() .demand(listVariableDescriptor.getStateDemand())) { var entityToOriginalPositionMap = - CollectionUtils.> newIdentityHashMap(affectedEntitySet.size()); + new IdentityHashMap>(affectedEntitySet.size()); for (var valueToRuin : ruinedValueList) { var position = listVariableStateSupply.getElementPosition(valueToRuin) .ensureAssigned(); @@ -55,7 +64,6 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) ignored -> new TreeSet<>()).add(new RuinedPosition(valueToRuin, position.index())); } - var nonRecordingScoreDirector = variableChangeRecordingScoreDirector.getBacking(); for (var entry : entityToOriginalPositionMap.entrySet()) { var entity = entry.getKey(); var originalPositionSet = entry.getValue(); @@ -80,20 +88,20 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) var constructionHeuristicPhase = (RuinRecreateConstructionHeuristicPhase) constructionHeuristicPhaseBuilder - .ensureThreadSafe(variableChangeRecordingScoreDirector.getBacking()) + .ensureThreadSafe(nonRecordingScoreDirector) .withElementsToRuin(entityToOriginalPositionMap.keySet()) .withElementsToRecreate(ruinedValueList) .build(); var nestedSolverScope = new SolverScope(solverScope.getClock()); nestedSolverScope.setSolver(solverScope.getSolver()); - nestedSolverScope.setScoreDirector(variableChangeRecordingScoreDirector.getBacking()); + nestedSolverScope.setScoreDirector(nonRecordingScoreDirector); constructionHeuristicPhase.solvingStarted(nestedSolverScope); constructionHeuristicPhase.solve(nestedSolverScope); constructionHeuristicPhase.solvingEnded(nestedSolverScope); scoreDirector.triggerVariableListeners(); - var entityToInsertedValuesMap = CollectionUtils.> newIdentityHashMap(0); + var entityToInsertedValuesMap = new IdentityHashMap>(); for (var entity : entityToOriginalPositionMap.keySet()) { entityToInsertedValuesMap.put(entity, new ArrayList<>()); } @@ -106,7 +114,6 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) entityToInsertedValuesMap.computeIfAbsent(position.entity(), ignored -> new ArrayList<>()).add(ruinedValue); } - var onlyRecordingChangesScoreDirector = variableChangeRecordingScoreDirector.getNonDelegating(); for (var entry : entityToInsertedValuesMap.entrySet()) { if (!entityToOriginalPositionMap.containsKey(entry.getKey())) { // The entity has not been evaluated while creating the entityToOriginalPositionMap, @@ -139,41 +146,38 @@ protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) onlyRecordingChangesScoreDirector.afterListVariableElementAssigned(listVariableDescriptor, element); } } - variableChangeRecordingScoreDirector.getBacking().getSupplyManager() + nonRecordingScoreDirector.getSupplyManager() .cancel(listVariableDescriptor.getStateDemand()); } } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return affectedEntitySet; } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return ruinedValueList; } @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return true; - } - - @Override - public Move rebase(ScoreDirector destinationScoreDirector) { - var rebasedRuinedValueList = AbstractMove.rebaseList(ruinedValueList, destinationScoreDirector); - var rebasedAffectedEntitySet = AbstractMove.rebaseSet(affectedEntitySet, destinationScoreDirector); - var rebasedListVariableDescriptor = ((InnerScoreDirector) destinationScoreDirector) - .getSolutionDescriptor().getListVariableDescriptor(); - return new ListRuinRecreateMove<>(rebasedListVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope, - rebasedRuinedValueList, rebasedAffectedEntitySet); + public Move rebase(Rebaser rebaser) { + var rebasedRuinedValueList = AbstractSelectorBasedMove.rebaseList(ruinedValueList, rebaser); + var rebasedAffectedEntitySet = AbstractSelectorBasedMove.rebaseSet(affectedEntitySet, rebaser); + var rebasedListVariableDescriptor = ((MoveDirector) rebaser) + .getScoreDirector() + .getSolutionDescriptor() + .getListVariableDescriptor(); + return new SelectorBasedListRuinRecreateMove<>(rebasedListVariableDescriptor, constructionHeuristicPhaseBuilder, + solverScope, rebasedRuinedValueList, rebasedAffectedEntitySet); } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ListRuinRecreateMove that)) + if (!(o instanceof SelectorBasedListRuinRecreateMove that)) return false; return Objects.equals(listVariableDescriptor, that.listVariableDescriptor) && Objects.equals(ruinedValueList, that.ruinedValueList) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/Acceptor.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/Acceptor.java index 7c53245a74d..2ad61b6558f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/Acceptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/Acceptor.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.impl.localsearch.decider.acceptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForager; import ai.timefold.solver.core.impl.localsearch.event.LocalSearchPhaseLifecycleListener; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope; +import ai.timefold.solver.core.preview.api.move.Move; /** * An Acceptor accepts or rejects a selected {@link Move}. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java index 182b4bbfd35..0d1ce8a6eca 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java @@ -10,7 +10,6 @@ import ai.timefold.solver.core.impl.domain.solution.descriptor.InnerGenuineVariableMetaModel; 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.heuristic.move.MoveAdapters; import ai.timefold.solver.core.impl.score.director.InnerScore; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; @@ -276,7 +275,7 @@ public boolean isValueInRange(GenuineVariableMetaModel move) { - MoveAdapters.unadapt(move).execute(this); + move.execute(this); externalScoreDirector.triggerVariableListeners(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java index fe2390fb3cc..4cec6300335 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java @@ -11,7 +11,7 @@ import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; 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.heuristic.move.AbstractMove; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.RevertableScoreDirector; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; @@ -130,7 +130,7 @@ public void afterListVariableChanged(ListVariableDescriptor variableD """ The fromIndex of afterListVariableChanged (%d) must match the fromIndex of its beforeListVariableChanged counterpart (%d). Maybe check implementation of your %s.""" - .formatted(fromIndex, requiredFromIndex, AbstractMove.class.getSimpleName())); + .formatted(fromIndex, requiredFromIndex, AbstractSelectorBasedMove.class.getSimpleName())); } } variableChanges.add(new ListVariableAfterChangeAction<>(entity, fromIndex, toIndex, variableDescriptor)); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java index 7b84f827618..10dfe528d9c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java @@ -65,21 +65,7 @@ public void solvingEnded(SolverScope solverScope) { @Override public Iterator> iterator() { - return new Iterator<>() { - - private final Iterator> delegate = - moveSelector.iterator(); - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public Move next() { - return delegate.next(); - } - }; + return moveSelector.iterator(); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsMoveSelector.java index df123e665eb..899bc754210 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsMoveSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsMoveSelector.java @@ -3,12 +3,11 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.MoveAdapters; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public final class NeighborhoodsMoveSelector extends AbstractMoveSelector { @@ -71,6 +70,7 @@ public void solvingEnded(SolverScope solverScope) { @Override public Iterator> iterator() { - return MoveAdapters.toLegacyMoveIterator(moveRepository.iterator()); + return moveRepository.iterator(); } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiOriginalMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiOriginalMoveIterator.java index 1fbfda46474..968a29d3fd0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiOriginalMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiOriginalMoveIterator.java @@ -5,6 +5,7 @@ import java.util.Objects; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniLeftDatasetInstance; import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniRightDatasetInstance; import ai.timefold.solver.core.preview.api.move.Move; @@ -78,7 +79,7 @@ public boolean hasNext() { var leftFact = leftTuple.getA(); var rightFact = rightTupleIterator.next().getA(); nextMove = context.buildMove(leftFact, rightFact); - if (nextMove instanceof ai.timefold.solver.core.impl.heuristic.move.Move legacyMove) { + if (nextMove instanceof AbstractSelectorBasedMove legacyMove) { throw new UnsupportedOperationException(""" Neighborhoods do not support legacy moves. Please refactor your code (%s) to use the new Move API.""" diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiRandomMoveIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiRandomMoveIterator.java index 219c4e7363b..2144b5ef14d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiRandomMoveIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiRandomMoveIterator.java @@ -8,6 +8,7 @@ import ai.timefold.solver.core.impl.bavet.common.index.UniqueRandomIterator; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.preview.api.move.Move; import org.jspecify.annotations.NullMarked; @@ -87,7 +88,7 @@ public boolean hasNext() { leftTuple.setStore(rightIteratorStoreIndex, null); } if (nextMove != null) { - if (nextMove instanceof ai.timefold.solver.core.impl.heuristic.move.Move legacyMove) { + if (nextMove instanceof AbstractSelectorBasedMove legacyMove) { throw new UnsupportedOperationException(""" Neighborhoods do not support legacy moves. Please refactor your code (%s) to use the new Move API.""" diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/constraint/DefaultIndictment.java b/core/src/main/java/ai/timefold/solver/core/impl/score/constraint/DefaultIndictment.java index c73151c29ff..11257fab42e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/constraint/DefaultIndictment.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/constraint/DefaultIndictment.java @@ -53,7 +53,7 @@ private List buildConstraintJustificationList() { return Collections.singletonList(constraintMatchSet.iterator().next().getJustification()); } default -> { - Set justificationSet = CollectionUtils.newLinkedHashSet(constraintMatchSetSize); + Set justificationSet = LinkedHashSet.newLinkedHashSet(constraintMatchSetSize); for (ConstraintMatch constraintMatch : constraintMatchSet) { justificationSet.add(constraintMatch.getJustification()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeState.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeState.java index a7ba76c7f4b..3e26babda6a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeState.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeState.java @@ -1,6 +1,8 @@ package ai.timefold.solver.core.impl.score.director; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -20,7 +22,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.ReachableValues.ReachableItemValue; import ai.timefold.solver.core.impl.heuristic.selector.common.ReachableValues.ReachableValuesIndex; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.MutableInt; import org.jspecify.annotations.NullMarked; @@ -178,8 +179,8 @@ public ValueRange getFromEntity(Entity_ entity, int entityCount, private Map, Value_>> ensureEntityMapIsInitialized(int entityCount) { if (fromEntityMap == null) { - fromEntityMap = CollectionUtils.newIdentityHashMap(entityCount); - valueRangeDeduplicationCache = CollectionUtils.newHashMap(entityCount); + fromEntityMap = new IdentityHashMap<>(entityCount); + valueRangeDeduplicationCache = HashMap.newHashMap(entityCount); } return fromEntityMap; } @@ -292,7 +293,7 @@ private ReachableValues fetchReachableValues(GenuineVariableDes } private static Map buildIndexMap(Iterator<@Nullable Type_> allValues, int size) { - Map indexMap = CollectionUtils.newHashMap(size); + Map indexMap = HashMap.newHashMap(size); var idx = 0; while (allValues.hasNext()) { var value = allValues.next(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java index dbbad741b15..047687bd8e0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java @@ -6,6 +6,7 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked public interface VariableDescriptorAwareScoreDirector @@ -21,7 +22,8 @@ public interface VariableDescriptorAwareScoreDirector void afterVariableChanged(VariableDescriptor variableDescriptor, Object entity); - default void changeVariableFacade(VariableDescriptor variableDescriptor, Object entity, Object newValue) { + default void changeVariableFacade(VariableDescriptor variableDescriptor, Object entity, + @Nullable Object newValue) { beforeVariableChanged(variableDescriptor, entity); variableDescriptor.setValue(entity, newValue); afterVariableChanged(variableDescriptor, entity); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/DefaultConstraintMetaModel.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/DefaultConstraintMetaModel.java index a2854b07003..af246fc2bc9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/DefaultConstraintMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/DefaultConstraintMetaModel.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -11,7 +12,6 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintRef; import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel; -import ai.timefold.solver.core.impl.util.CollectionUtils; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -23,7 +23,8 @@ record DefaultConstraintMetaModel( public static ConstraintMetaModel of(List constraints) { var constraintCount = constraints.size(); // Preserve iteration order by using LinkedHashMap. - var perRefMap = CollectionUtils. newLinkedHashMap(constraintCount); + + var perRefMap = LinkedHashMap. newLinkedHashMap(constraintCount); var perGroupMap = new TreeMap>(); for (var constraint : constraints) { perRefMap.put(constraint.getConstraintRef(), constraint); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java index c955439eeda..2ed21c9ba71 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.bavet; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -25,7 +26,6 @@ import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner; -import ai.timefold.solver.core.impl.util.CollectionUtils; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -71,7 +71,7 @@ public BavetConstraintSession buildSession(Solution_ workingSolution, var scoreDefinition = solutionDescriptor. getScoreDefinition(); var zeroScore = scoreDefinition.getZeroScore(); var constraintStreamSet = new LinkedHashSet>(); - var constraintWeightMap = CollectionUtils. newHashMap(constraints.size()); + var constraintWeightMap = HashMap. newHashMap(constraints.size()); // Only log constraint weights if logging is enabled; otherwise we don't need to build the string. var constraintWeightLoggingEnabled = !scoreDirectorDerived && LOGGER.isEnabledForLevel(CONSTRAINT_WEIGHT_LOGGING_LEVEL); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/inliner/AbstractScoreInliner.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/inliner/AbstractScoreInliner.java index 10e06077240..16720288848 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/inliner/AbstractScoreInliner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/inliner/AbstractScoreInliner.java @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.common.inliner; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -25,7 +26,6 @@ import ai.timefold.solver.core.impl.score.definition.SimpleBigDecimalScoreDefinition; import ai.timefold.solver.core.impl.score.definition.SimpleScoreDefinition; import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraint; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.ElementAwareLinkedList; import org.jspecify.annotations.NullMarked; @@ -79,7 +79,7 @@ protected AbstractScoreInliner(Map constraintWeightMap, Cons constraintWeightMap.forEach(this::validateConstraintWeight); this.constraintWeightMap = constraintWeightMap; if (constraintMatchPolicy.isEnabled()) { - this.constraintMatchMap = CollectionUtils.newIdentityHashMap(constraintWeightMap.size()); + this.constraintMatchMap = new IdentityHashMap<>(constraintWeightMap.size()); for (var constraint : constraintWeightMap.keySet()) { // Ensure that even constraints without matches have their entry. this.constraintMatchMap.put(constraint, new ElementAwareLinkedList<>()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/AssignmentProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/AssignmentProcessor.java index 89fd8ec92d9..54f2058ac92 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/AssignmentProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/AssignmentProcessor.java @@ -14,8 +14,8 @@ import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicPhaseScope; import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicStepScope; import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListUnassignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListUnassignMove; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; @@ -61,7 +61,7 @@ public List apply(InnerScoreDirector scoreDi if (elementPosition instanceof PositionInList positionInList) { // Unassign the cloned element. var entity = positionInList.entity(); var index = positionInList.index(); - moveDirector.execute(new ListUnassignMove<>(listVariableDescriptor, entity, index)); + moveDirector.execute(new SelectorBasedListUnassignMove<>(listVariableDescriptor, entity, index)); } } } else { @@ -73,7 +73,7 @@ public List apply(InnerScoreDirector scoreDi continue; } // Uninitialize the basic variable. - moveDirector.execute(new ChangeMove<>(basicVariableDescriptor, clonedElement, null)); + moveDirector.execute(new SelectorBasedChangeMove<>(basicVariableDescriptor, clonedElement, null)); } } scoreDirector.triggerVariableListeners(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/CollectionUtils.java b/core/src/main/java/ai/timefold/solver/core/impl/util/CollectionUtils.java index 98e63c591d6..890f4c22388 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/CollectionUtils.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/CollectionUtils.java @@ -3,15 +3,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import org.jspecify.annotations.NullMarked; + +@NullMarked public final class CollectionUtils { /** @@ -37,7 +36,7 @@ public static List copy(List originalList, boolean reverse) { } case 1 -> { List singletonList = new ArrayList<>(1); - singletonList.add(originalList.get(0)); + singletonList.add(originalList.getFirst()); return singletonList; } case 2 -> { @@ -83,7 +82,7 @@ public static List toDistinctList(Collection collection) { * while still maintaining the original order of the collection. */ var resultList = new ArrayList(size); - var set = newHashSet(size); + var set = HashSet.newHashSet(size); for (T element : collection) { if (set.add(element)) { resultList.add(element); @@ -95,34 +94,8 @@ public static List toDistinctList(Collection collection) { } } - public static Set newHashSet(int size) { - return new HashSet<>(calculateCapacityForDefaultLoadFactor(size)); - } - - private static int calculateCapacityForDefaultLoadFactor(int numElements) { - // This guarantees the set/map will never need to grow. - return (int) Math.min(Math.ceil(numElements / 0.75d), Integer.MAX_VALUE); - } - public static Set newIdentityHashSet(int size) { - return Collections.newSetFromMap(CollectionUtils.newIdentityHashMap(size)); - } - - public static Set newLinkedHashSet(int size) { - return new LinkedHashSet<>(calculateCapacityForDefaultLoadFactor(size)); - } - - public static Map newHashMap(int size) { - return new HashMap<>(calculateCapacityForDefaultLoadFactor(size)); - } - - public static Map newIdentityHashMap(int size) { - return new IdentityHashMap<>(calculateCapacityForDefaultLoadFactor(size)); - } - - public static Map newLinkedHashMap(int size) { - return new LinkedHashMap<>(calculateCapacityForDefaultLoadFactor(size)); - + return Collections.newSetFromMap(new IdentityHashMap<>(size)); } private CollectionUtils() { diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java index fb67588058b..3119a54f67c 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java @@ -1,9 +1,7 @@ package ai.timefold.solver.core.preview.api.move; import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.common.PlanningId; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; @@ -13,6 +11,7 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A Move represents a change of 1 or more {@link PlanningVariable}s of 1 or more {@link PlanningEntity}s @@ -100,17 +99,13 @@ default Move rebase(Rebaser rebaser) { * Required for entity tabu. *

* This method is only called after {@link #execute(MutableSolutionView)}, which might affect the return values. - *

* Duplicate entries in the returned {@link Collection} are best avoided. - * The returned {@link Collection} is recommended to be in a stable order. - * For example, use {@link List} or {@link LinkedHashSet}, but not {@link HashSet}. - *

* The default implementation throws an {@link UnsupportedOperationException}, * making tabu search impossible unless the move class implements this method. * * @return Each entity only once. */ - default Collection getPlanningEntities() { + default SequencedCollection getPlanningEntities() { throw new UnsupportedOperationException( "Move class (%s) doesn't implement the getPlanningEntities() method, so Entity Tabu Search is impossible." .formatted(getClass())); @@ -121,17 +116,13 @@ default Collection getPlanningEntities() { * Required for value tabu. *

* This method is only called after {@link #execute(MutableSolutionView)}, which might affect the return values. - *

* Duplicate entries in the returned {@link Collection} are best avoided. - * The returned {@link Collection} is recommended to be in a stable order. - * For example, use {@link List} or {@link LinkedHashSet}, but not {@link HashSet}. - *

* The default implementation throws an {@link UnsupportedOperationException}, * making tabu search impossible unless the move class implements this method. * * @return Each value only once. May contain null. */ - default Collection getPlanningValues() { + default SequencedCollection<@Nullable Object> getPlanningValues() { throw new UnsupportedOperationException( "Move class (%s) doesn't implement the getPlanningValues() method, so Value Tabu Search is impossible." .formatted(getClass())); diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java index e1f5f90fe36..a439221baa7 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.preview.api.move.builtin; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -57,12 +57,12 @@ public ChangeMove rebase(Rebaser rebaser) { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return Collections.singletonList(entity); } @Override - public Collection getPlanningValues() { + public SequencedCollection<@Nullable Object> getPlanningValues() { return Collections.singletonList(toPlanningValue); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java index 6b2c18a185c..e892a7b4145 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.preview.api.move.builtin; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.SequencedCollection; import java.util.stream.Collectors; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; import ai.timefold.solver.core.preview.api.move.Rebaser; @@ -63,8 +63,8 @@ public Move rebase(Rebaser rebaser) { } @Override - public Collection getPlanningEntities() { - var entities = CollectionUtils.newLinkedHashSet(moves.length * 2); + public SequencedCollection getPlanningEntities() { + var entities = LinkedHashSet.newLinkedHashSet(moves.length * 2); for (var move : moves) { entities.addAll(move.getPlanningEntities()); } @@ -72,8 +72,8 @@ public Collection getPlanningEntities() { } @Override - public Collection getPlanningValues() { - var values = CollectionUtils.newLinkedHashSet(moves.length * 2); + public SequencedCollection getPlanningValues() { + var values = LinkedHashSet.newLinkedHashSet(moves.length * 2); for (var move : moves) { values.addAll(move.getPlanningValues()); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java index 3bbc40bf9e7..7e035012428 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java @@ -1,8 +1,8 @@ package ai.timefold.solver.core.preview.api.move.builtin; -import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; @@ -43,12 +43,12 @@ public Move rebase(Rebaser rebaser) { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(destinationEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return List.of(planningValue); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java index 740756a08ba..1ec68f44633 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java @@ -1,9 +1,8 @@ package ai.timefold.solver.core.preview.api.move.builtin; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -142,17 +141,17 @@ public ListChangeMove rebase(Rebaser rebaser) { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { if (sourceEntity == destinationEntity) { - return Collections.singleton(sourceEntity); + return List.of(sourceEntity); } else { return List.of(sourceEntity, destinationEntity); } } @Override - public Collection getPlanningValues() { - return Collections.singleton(getMovedValue()); + public SequencedCollection getPlanningValues() { + return List.of(getMovedValue()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java index 790efbc76c6..f037792d780 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java @@ -1,10 +1,10 @@ package ai.timefold.solver.core.preview.api.move.builtin; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -145,12 +145,12 @@ public List> variableM } @Override - public Collection getPlanningEntities() { - return leftEntity == rightEntity ? Collections.singleton(leftEntity) : Arrays.asList(leftEntity, rightEntity); + public SequencedCollection getPlanningEntities() { + return leftEntity == rightEntity ? Collections.singletonList(leftEntity) : Arrays.asList(leftEntity, rightEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return Arrays.asList(getLeftValueCache(), getRightValueCache()); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java index 99682736ab1..6d6b83b73c5 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.preview.api.move.builtin; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; @@ -40,17 +40,17 @@ public void execute(MutableSolutionView solutionView) { @Override public Move rebase(Rebaser rebaser) { - return new ListUnassignMove<>(variableMetaModel, rebaser.rebase(sourceEntity), sourceIndex); + return new ListUnassignMove<>(variableMetaModel, Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex); } @Override - public Collection getPlanningEntities() { - return Collections.singleton(sourceEntity); + public SequencedCollection getPlanningEntities() { + return List.of(sourceEntity); } @Override - public Collection getPlanningValues() { - return Collections.singleton(getUnassignedValue()); + public SequencedCollection getPlanningValues() { + return List.of(getUnassignedValue()); } private Value_ getUnassignedValue() { diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java index d0339a6e8ce..afc1a0e748d 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java @@ -1,10 +1,10 @@ package ai.timefold.solver.core.preview.api.move.builtin; import java.util.ArrayList; -import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -116,12 +116,12 @@ public void execute(MutableSolutionView solutionView) { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return List.of(leftEntity, rightEntity); } @Override - public Collection getPlanningValues() { + public SequencedCollection<@Nullable Object> getPlanningValues() { return new LinkedHashSet<>(getCachedValues()); // Not using Set.of(), as values may be null. } diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java index f7912816be7..7a79200efba 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java @@ -34,12 +34,12 @@ import ai.timefold.solver.core.api.solver.phase.PhaseCommand; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveListFactory; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO; import ai.timefold.solver.core.impl.io.jaxb.TimefoldXmlSerializationException; import ai.timefold.solver.core.impl.partitionedsearch.partitioner.SolutionPartitioner; @@ -472,10 +472,11 @@ public abstract static class DummyEntityFilter implements SelectionFilter> { + implements SelectionFilter> { } - public abstract static class DummyMoveIteratorFactory implements MoveIteratorFactory { + public abstract static class DummyMoveIteratorFactory + implements MoveIteratorFactory { } public abstract static class DummyMoveListFactory implements MoveListFactory { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PlacementAssertions.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PlacementAssertions.java index 0524e327e10..b5af08268e5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PlacementAssertions.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PlacementAssertions.java @@ -3,36 +3,34 @@ import static ai.timefold.solver.core.testutil.PlannerAssert.assertCode; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Iterator; - import ai.timefold.solver.core.impl.constructionheuristic.placer.Placement; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.preview.api.move.Move; final class PlacementAssertions { static void assertEntityPlacement(Placement placement, String entityCode, String... valueCodes) { - Iterator> iterator = placement.iterator(); + var iterator = placement.iterator(); assertThat(iterator).isNotNull(); - for (String valueCode : valueCodes) { + for (var valueCode : valueCodes) { assertThat(iterator).hasNext(); - ChangeMove move = adapt(iterator.next()); + var move = adapt(iterator.next()); assertCode(entityCode, move.getEntity()); assertCode(valueCode, move.getToPlanningValue()); } assertThat(iterator).isExhausted(); } - static ChangeMove adapt(Move move) { - return (ChangeMove) move; + static SelectorBasedChangeMove adapt(Move move) { + return (SelectorBasedChangeMove) move; } static void assertValuePlacement(Placement placement, String valueCode, String... entityCodes) { - Iterator> iterator = placement.iterator(); + var iterator = placement.iterator(); assertThat(iterator).isNotNull(); - for (String entityCode : entityCodes) { + for (var entityCode : entityCodes) { assertThat(iterator).hasNext(); - ChangeMove move = adapt(iterator.next()); + var move = adapt(iterator.next()); assertCode(entityCode, move.getEntity()); assertCode(valueCode, move.getToPlanningValue()); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PooledEntityPlacerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PooledEntityPlacerTest.java index 1cf8e49afb5..536682afb7b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PooledEntityPlacerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/PooledEntityPlacerTest.java @@ -16,7 +16,7 @@ import ai.timefold.solver.core.impl.constructionheuristic.placer.Placement; import ai.timefold.solver.core.impl.constructionheuristic.placer.PooledEntityPlacer; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; @@ -30,7 +30,7 @@ class PooledEntityPlacerTest { @Test void oneMoveSelector() { var moveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("b1")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("b1")); var placer = new PooledEntityPlacer<>(null, null, moveSelector); @@ -91,7 +91,8 @@ void oneMoveSelector() { @Test void copy() { var moveSelector = SelectorTestUtils - .mockMoveSelector(new DummyMove("a1"), new DummyMove("a2"), new DummyMove("b1")); + .mockMoveSelector(new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), + new SelectorBasedDummyMove("b1")); var factory = mock(EntityPlacerFactory.class); var configPolicy = mock(HeuristicConfigPolicy.class); assertThatThrownBy(() -> new PooledEntityPlacer<>(null, null, moveSelector).copy()) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java index 764527dad34..db9fe51e7f9 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java @@ -9,12 +9,12 @@ import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.api.solver.SolutionManager; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListAssignMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListSwapMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListUnassignMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SubListChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SubListSwapMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListAssignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListSwapMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListUnassignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedSubListChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedSubListSwapMove; import ai.timefold.solver.core.impl.score.director.InnerScore; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.testdomain.list.shadowhistory.TestdataListEntityWithShadowHistory; @@ -86,35 +86,35 @@ static void assertNextHistory(TestdataListValueWithShadowHistory element, void doChangeMove(TestdataListEntityWithShadowHistory sourceEntity, int sourceIndex, TestdataListEntityWithShadowHistory destinationEntity, int destinationIndex) { - new ListChangeMove<>(variableDescriptor, sourceEntity, sourceIndex, destinationEntity, destinationIndex) - .doMoveOnly(scoreDirector); + new SelectorBasedListChangeMove<>(variableDescriptor, sourceEntity, sourceIndex, destinationEntity, destinationIndex) + .execute(scoreDirector.getMoveDirector()); } void doSwapMove(TestdataListEntityWithShadowHistory leftEntity, int leftIndex, TestdataListEntityWithShadowHistory rightEntity, int rightIndex) { - new ListSwapMove<>(variableDescriptor, leftEntity, leftIndex, rightEntity, rightIndex) - .doMoveOnly(scoreDirector); + new SelectorBasedListSwapMove<>(variableDescriptor, leftEntity, leftIndex, rightEntity, rightIndex) + .execute(scoreDirector.getMoveDirector()); } void doSubListChangeMove(TestdataListEntityWithShadowHistory sourceEntity, int fromIndex, int toIndex, TestdataListEntityWithShadowHistory destinationEntity, int destinationIndex, boolean reversing) { - new SubListChangeMove<>( + new SelectorBasedSubListChangeMove<>( variableDescriptor, sourceEntity, fromIndex, toIndex - fromIndex, destinationEntity, destinationIndex, reversing) - .doMoveOnly(scoreDirector); + .execute(scoreDirector.getMoveDirector()); } void doSubListSwapMove( TestdataListEntityWithShadowHistory leftEntity, int leftFromIndex, int leftToIndex, TestdataListEntityWithShadowHistory rightEntity, int rightFromIndex, int rightToIndex, boolean reversing) { - new SubListSwapMove<>( + new SelectorBasedSubListSwapMove<>( variableDescriptor, leftEntity, leftFromIndex, leftToIndex, rightEntity, rightFromIndex, rightToIndex, reversing) - .doMoveOnly(scoreDirector); + .execute(scoreDirector.getMoveDirector()); } @Test @@ -186,7 +186,7 @@ void addAndRemoveElement() { scoreDirector.setWorkingSolution(solution); assertThat(scoreDirector.calculateScore()).isEqualTo(InnerScore.withUnassignedCount(SimpleScore.ZERO, 1)); - new ListAssignMove<>(variableDescriptor, x, ann, 2).doMoveOnly(scoreDirector); + new SelectorBasedListAssignMove<>(variableDescriptor, x, ann, 2).execute(scoreDirector.getMoveDirector()); assertThat(ann.getValueList()).containsExactly(a, b, x, c); @@ -210,7 +210,7 @@ void addAndRemoveElement() { assertNextHistory(x, c); assertEmptyNextHistory(c); - new ListUnassignMove<>(variableDescriptor, ann, 1).doMoveOnly(scoreDirector); + new SelectorBasedListUnassignMove<>(variableDescriptor, ann, 1).execute(scoreDirector.getMoveDirector()); assertThat(ann.getValueList()).containsExactly(a, x, c); @@ -330,7 +330,7 @@ void moveElementToAnotherEntityChangeIndex() { scoreDirector.setWorkingSolution(buildSolution(ann, bob)); - new ListChangeMove<>(variableDescriptor, ann, 0, bob, 1).doMoveOnly(scoreDirector); + new SelectorBasedListChangeMove<>(variableDescriptor, ann, 0, bob, 1).execute(scoreDirector.getMoveDirector()); assertThat(ann.getValueList()).containsExactly(b, c); assertThat(bob.getValueList()).containsExactly(x, a, y); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMoveTest.java deleted file mode 100644 index 5ea3a3f52bf..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/CompositeMoveTest.java +++ /dev/null @@ -1,189 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockRebasingScoreDirector; -import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockScoreDirector; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SwapMove; -import ai.timefold.solver.core.testdomain.TestdataEntity; -import ai.timefold.solver.core.testdomain.TestdataSolution; -import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testutil.PlannerTestUtils; - -import org.junit.jupiter.api.Test; - -class CompositeMoveTest { - - @Test - void doMove() { - var scoreDirector = PlannerTestUtils.mockScoreDirector(TestdataSolution.buildSolutionDescriptor()); - var a = mock(DummyMove.class); - when(a.isMoveDoable(any())).thenReturn(true); - var b = mock(DummyMove.class); - when(b.isMoveDoable(any())).thenReturn(true); - var c = mock(DummyMove.class); - when(c.isMoveDoable(any())).thenReturn(true); - var move = new CompositeMove<>(a, b, c); - move.doMoveOnly(scoreDirector); - verify(a, times(1)).doMoveOnly(any()); - verify(b, times(1)).doMoveOnly(any()); - verify(c, times(1)).doMoveOnly(any()); - } - - @Test - void rebase() { - var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); - - var v1 = new TestdataValue("v1"); - var v2 = new TestdataValue("v2"); - var e1 = new TestdataEntity("e1", v1); - var e2 = new TestdataEntity("e2", null); - var e3 = new TestdataEntity("e3", v1); - - var destinationV1 = new TestdataValue("v1"); - var destinationV2 = new TestdataValue("v2"); - var destinationE1 = new TestdataEntity("e1", destinationV1); - var destinationE2 = new TestdataEntity("e2", null); - var destinationE3 = new TestdataEntity("e3", destinationV1); - - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( - variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { - { v1, destinationV1 }, - { v2, destinationV2 }, - { e1, destinationE1 }, - { e2, destinationE2 }, - { e3, destinationE3 }, - }); - - var a = new ChangeMove<>(variableDescriptor, e1, v2); - var b = new ChangeMove<>(variableDescriptor, e2, v1); - var rebaseMove = new CompositeMove<>(a, b).rebase(destinationScoreDirector); - var rebasedChildMoves = rebaseMove.getMoves(); - assertThat(rebasedChildMoves).hasSize(2); - var rebasedA = (ChangeMove) rebasedChildMoves[0]; - assertThat(rebasedA.getEntity()).isSameAs(destinationE1); - assertThat(rebasedA.getToPlanningValue()).isSameAs(destinationV2); - var rebasedB = (ChangeMove) rebasedChildMoves[1]; - assertThat(rebasedB.getEntity()).isSameAs(destinationE2); - assertThat(rebasedB.getToPlanningValue()).isSameAs(destinationV1); - } - - @Test - void buildEmptyMove() { - assertThat(CompositeMove.buildMove(new ArrayList<>())) - .isInstanceOf(NoChangeMove.class); - assertThat(CompositeMove.buildMove()) - .isInstanceOf(NoChangeMove.class); - } - - @Test - void buildOneElemMove() { - var tmpMove = new DummyMove(); - var move = CompositeMove.buildMove(Collections.singletonList(tmpMove)); - assertThat(move) - .isInstanceOf(DummyMove.class); - - move = CompositeMove.buildMove(tmpMove); - assertThat(move) - .isInstanceOf(DummyMove.class); - } - - @Test - void buildTwoElemMove() { - Move first = (Move) new DummyMove(); - Move second = NoChangeMove.getInstance(); - var move = CompositeMove.buildMove(first, second); - assertThat(move) - .isInstanceOf(CompositeMove.class); - assertThat(((CompositeMove) move).getMoves()[0]) - .isInstanceOf(DummyMove.class); - assertThat(((CompositeMove) move).getMoves()[1]) - .isInstanceOf(NoChangeMove.class); - - move = CompositeMove.buildMove(first, second); - assertThat(move) - .isInstanceOf(CompositeMove.class); - assertThat(((CompositeMove) move).getMoves()[0]) - .isInstanceOf(DummyMove.class); - assertThat(((CompositeMove) move).getMoves()[1]) - .isInstanceOf(NoChangeMove.class); - } - - @Test - void isMoveDoable() { - var scoreDirector = PlannerTestUtils.mockScoreDirector(TestdataSolution.buildSolutionDescriptor()); - - var first = new DummyMove(); - var second = new DummyMove(); - var move = CompositeMove.buildMove(first, second); - assertThat(move.isMoveDoable(scoreDirector)).isTrue(); - - first = new DummyMove(); - second = new NotDoableDummyMove(); - move = CompositeMove.buildMove(first, second); - assertThat(move.isMoveDoable(scoreDirector)).isTrue(); - - first = new NotDoableDummyMove(); - second = new DummyMove(); - move = CompositeMove.buildMove(first, second); - assertThat(move.isMoveDoable(scoreDirector)).isTrue(); - - first = new NotDoableDummyMove(); - second = new NotDoableDummyMove(); - move = CompositeMove.buildMove(first, second); - assertThat(move.isMoveDoable(scoreDirector)).isFalse(); - } - - @Test - void equals() { - var first = (Move) new DummyMove(); - var second = (Move) NoChangeMove.getInstance(); - var move = CompositeMove.buildMove(Arrays.asList(first, second)); - var other = CompositeMove.buildMove(first, second); - assertThat(move).isEqualTo(other); - - move = CompositeMove.buildMove(first, second); - other = CompositeMove.buildMove(second, first); - assertThat(move).isNotEqualTo(other); - assertThat(move).isNotEqualTo(new DummyMove()); - assertThat(move).isEqualTo(move); - } - - @Test - void interconnectedChildMoves() { - var solution = new TestdataSolution("s1"); - var v1 = new TestdataValue("v1"); - var v2 = new TestdataValue("v2"); - var v3 = new TestdataValue("v3"); - solution.setValueList(Arrays.asList(v1, v2, v3)); - var e1 = new TestdataEntity("e1", v1); - var e2 = new TestdataEntity("e2", v2); - solution.setEntityList(Arrays.asList(e1, e2)); - - var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); - var first = new SwapMove<>(Collections.singletonList(variableDescriptor), e1, e2); - var second = new ChangeMove<>(variableDescriptor, e1, v3); - var move = CompositeMove.buildMove(first, second); - - assertThat(e1.getValue()).isSameAs(v1); - assertThat(e2.getValue()).isSameAs(v2); - - var scoreDirector = mockScoreDirector(variableDescriptor.getEntityDescriptor().getSolutionDescriptor()); - move.doMoveOnly(scoreDirector); - - assertThat(e1.getValue()).isSameAs(v3); - assertThat(e2.getValue()).isSameAs(v1); - } - -} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/DummyMove.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/DummyMove.java deleted file mode 100644 index 0cc96229cfd..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/DummyMove.java +++ /dev/null @@ -1,55 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.move; - -import java.util.Collection; -import java.util.Collections; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.testdomain.TestdataSolution; -import ai.timefold.solver.core.testutil.CodeAssertable; - -public class DummyMove extends AbstractMove implements CodeAssertable { - - protected String code; - - public DummyMove() { - } - - public DummyMove(String code) { - this.code = code; - } - - @Override - public String getCode() { - return code; - } - - // ************************************************************************ - // Complex methods - // ************************************************************************ - - @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return true; - } - - @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - // do nothing - } - - @Override - public Collection getPlanningEntities() { - return Collections.emptyList(); - } - - @Override - public Collection getPlanningValues() { - return Collections.emptyList(); - } - - @Override - public String toString() { - return code; - } - -} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMoveTest.java new file mode 100644 index 00000000000..a853126edc8 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMoveTest.java @@ -0,0 +1,192 @@ +package ai.timefold.solver.core.impl.heuristic.move; + +import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockRebasingScoreDirector; +import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockScoreDirector; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedSwapMove; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.testdomain.TestdataEntity; +import ai.timefold.solver.core.testdomain.TestdataSolution; +import ai.timefold.solver.core.testdomain.TestdataValue; +import ai.timefold.solver.core.testutil.PlannerTestUtils; + +import org.junit.jupiter.api.Test; + +class SelectorBasedCompositeMoveTest { + + @Test + void doMove() { + var scoreDirector = PlannerTestUtils.mockScoreDirector(TestdataSolution.buildSolutionDescriptor()); + var a = mock(SelectorBasedDummyMove.class); + when(a.isMoveDoable(any())).thenReturn(true); + var b = mock(SelectorBasedDummyMove.class); + when(b.isMoveDoable(any())).thenReturn(true); + var c = mock(SelectorBasedDummyMove.class); + when(c.isMoveDoable(any())).thenReturn(true); + var move = new SelectorBasedCompositeMove<>(a, b, c); + move.execute(scoreDirector); + verify(a, times(1)).execute(any(VariableDescriptorAwareScoreDirector.class)); + verify(b, times(1)).execute(any(VariableDescriptorAwareScoreDirector.class)); + verify(c, times(1)).execute(any(VariableDescriptorAwareScoreDirector.class)); + } + + @Test + void rebase() { + var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); + + var v1 = new TestdataValue("v1"); + var v2 = new TestdataValue("v2"); + var e1 = new TestdataEntity("e1", v1); + var e2 = new TestdataEntity("e2", null); + var e3 = new TestdataEntity("e3", v1); + + var destinationV1 = new TestdataValue("v1"); + var destinationV2 = new TestdataValue("v2"); + var destinationE1 = new TestdataEntity("e1", destinationV1); + var destinationE2 = new TestdataEntity("e2", null); + var destinationE3 = new TestdataEntity("e3", destinationV1); + + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { + { v1, destinationV1 }, + { v2, destinationV2 }, + { e1, destinationE1 }, + { e2, destinationE2 }, + { e3, destinationE3 }, + }); + + var a = new SelectorBasedChangeMove<>(variableDescriptor, e1, v2); + var b = new SelectorBasedChangeMove<>(variableDescriptor, e2, v1); + var rebaseMove = new SelectorBasedCompositeMove<>(a, b).rebase(destinationScoreDirector.getMoveDirector()); + var rebasedChildMoves = rebaseMove.getMoves(); + assertThat(rebasedChildMoves).hasSize(2); + var rebasedA = (SelectorBasedChangeMove) rebasedChildMoves[0]; + assertThat(rebasedA.getEntity()).isSameAs(destinationE1); + assertThat(rebasedA.getToPlanningValue()).isSameAs(destinationV2); + var rebasedB = (SelectorBasedChangeMove) rebasedChildMoves[1]; + assertThat(rebasedB.getEntity()).isSameAs(destinationE2); + assertThat(rebasedB.getToPlanningValue()).isSameAs(destinationV1); + } + + @Test + void buildEmptyMove() { + assertThat(SelectorBasedCompositeMove.buildMove(new ArrayList<>())) + .isInstanceOf(SelectorBasedNoChangeMove.class); + assertThat(SelectorBasedCompositeMove.buildMove()) + .isInstanceOf(SelectorBasedNoChangeMove.class); + } + + @Test + void buildOneElemMove() { + var tmpMove = new SelectorBasedDummyMove(); + var move = SelectorBasedCompositeMove.buildMove(Collections.singletonList(tmpMove)); + assertThat(move) + .isInstanceOf(SelectorBasedDummyMove.class); + + move = SelectorBasedCompositeMove.buildMove(tmpMove); + assertThat(move) + .isInstanceOf(SelectorBasedDummyMove.class); + } + + @Test + void buildTwoElemMove() { + Move first = (Move) new SelectorBasedDummyMove(); + Move second = SelectorBasedNoChangeMove.getInstance(); + var move = SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move) + .isInstanceOf(SelectorBasedCompositeMove.class); + assertThat(((SelectorBasedCompositeMove) move).getMoves()[0]) + .isInstanceOf(SelectorBasedDummyMove.class); + assertThat(((SelectorBasedCompositeMove) move).getMoves()[1]) + .isInstanceOf(SelectorBasedNoChangeMove.class); + + move = SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move) + .isInstanceOf(SelectorBasedCompositeMove.class); + assertThat(((SelectorBasedCompositeMove) move).getMoves()[0]) + .isInstanceOf(SelectorBasedDummyMove.class); + assertThat(((SelectorBasedCompositeMove) move).getMoves()[1]) + .isInstanceOf(SelectorBasedNoChangeMove.class); + } + + @Test + void isMoveDoable() { + var scoreDirector = PlannerTestUtils.mockScoreDirector(TestdataSolution.buildSolutionDescriptor()); + + var first = new SelectorBasedDummyMove(); + var second = new SelectorBasedDummyMove(); + var move = (SelectorBasedCompositeMove) SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move.isMoveDoable(scoreDirector)).isTrue(); + + first = new SelectorBasedDummyMove(); + second = new SelectorBasedNotDoableDummyMove(); + move = (SelectorBasedCompositeMove) SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move.isMoveDoable(scoreDirector)).isTrue(); + + first = new SelectorBasedNotDoableDummyMove(); + second = new SelectorBasedDummyMove(); + move = (SelectorBasedCompositeMove) SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move.isMoveDoable(scoreDirector)).isTrue(); + + first = new SelectorBasedNotDoableDummyMove(); + second = new SelectorBasedNotDoableDummyMove(); + move = (SelectorBasedCompositeMove) SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move.isMoveDoable(scoreDirector)).isFalse(); + } + + @Test + void equals() { + var first = (Move) new SelectorBasedDummyMove(); + var second = (Move) SelectorBasedNoChangeMove.getInstance(); + var move = SelectorBasedCompositeMove.buildMove(Arrays.asList(first, second)); + var other = SelectorBasedCompositeMove.buildMove(first, second); + assertThat(move).isEqualTo(other); + + move = SelectorBasedCompositeMove.buildMove(first, second); + other = SelectorBasedCompositeMove.buildMove(second, first); + assertThat(move) + .isNotEqualTo(other) + .isNotEqualTo(new SelectorBasedDummyMove()); + } + + @Test + void interconnectedChildMoves() { + var solution = new TestdataSolution("s1"); + var v1 = new TestdataValue("v1"); + var v2 = new TestdataValue("v2"); + var v3 = new TestdataValue("v3"); + solution.setValueList(Arrays.asList(v1, v2, v3)); + var e1 = new TestdataEntity("e1", v1); + var e2 = new TestdataEntity("e2", v2); + solution.setEntityList(Arrays.asList(e1, e2)); + + var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); + var first = new SelectorBasedChangeMove<>(variableDescriptor, e1, v3); + var second = new SelectorBasedSwapMove<>(Collections.singletonList(variableDescriptor), e1, e2); + var move = SelectorBasedCompositeMove.buildMove(first, second); + + assertThat(e1.getValue()).isSameAs(v1); + assertThat(e2.getValue()).isSameAs(v2); + + try (var scoreDirector = mockScoreDirector(variableDescriptor.getEntityDescriptor().getSolutionDescriptor())) { + move.execute(scoreDirector.getMoveDirector()); + } + + assertThat(e1.getValue()).isSameAs(v2); + assertThat(e2.getValue()).isSameAs(v3); + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedDummyMove.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedDummyMove.java new file mode 100644 index 00000000000..bec71ac2bd1 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedDummyMove.java @@ -0,0 +1,54 @@ +package ai.timefold.solver.core.impl.heuristic.move; + +import java.util.Collections; +import java.util.SequencedCollection; + +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; +import ai.timefold.solver.core.testdomain.TestdataSolution; +import ai.timefold.solver.core.testutil.CodeAssertable; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class SelectorBasedDummyMove extends AbstractSelectorBasedMove implements CodeAssertable { + + protected @Nullable String code; + + public SelectorBasedDummyMove() { + } + + public SelectorBasedDummyMove(String code) { + this.code = code; + } + + @Override + public @Nullable String getCode() { + return code; + } + + // ************************************************************************ + // Complex methods + // ************************************************************************ + + @Override + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + // do nothing + } + + @Override + public SequencedCollection getPlanningEntities() { + return Collections.emptyList(); + } + + @Override + public SequencedCollection getPlanningValues() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return code; + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMoveTest.java similarity index 65% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMoveTest.java index 9327d9a7729..26f1b4e76c6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NoChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMoveTest.java @@ -7,18 +7,18 @@ import org.junit.jupiter.api.Test; -class NoChangeMoveTest { +class SelectorBasedNoChangeMoveTest { @Test void isMoveDoable() { - assertThat(NoChangeMove.getInstance().isMoveDoable(null)).isFalse(); + assertThat(SelectorBasedNoChangeMove.getInstance().isMoveDoable(null)).isFalse(); } @Test void rebase() { var destinationScoreDirector = mockRebasingScoreDirector(TestdataSolution.buildSolutionDescriptor(), new Object[][] {}); - var move = NoChangeMove. getInstance(); - var rebasedMove = move.rebase(destinationScoreDirector); + var move = SelectorBasedNoChangeMove. getInstance(); + var rebasedMove = move.rebase(destinationScoreDirector.getMoveDirector()); assertThat(rebasedMove).isSameAs(move); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NotDoableDummyMove.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java similarity index 58% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NotDoableDummyMove.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java index 536d8780fa5..d34e4ab1685 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/NotDoableDummyMove.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java @@ -3,12 +3,15 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.testdomain.TestdataSolution; -public class NotDoableDummyMove extends DummyMove { +import org.jspecify.annotations.NullMarked; - public NotDoableDummyMove() { +@NullMarked +public class SelectorBasedNotDoableDummyMove extends SelectorBasedDummyMove { + + public SelectorBasedNotDoableDummyMove() { } - public NotDoableDummyMove(String code) { + public SelectorBasedNotDoableDummyMove(String code) { super(code); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/SelectorTestUtils.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/SelectorTestUtils.java index 16cedbd3bb0..1ea63a6df73 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/SelectorTestUtils.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/SelectorTestUtils.java @@ -16,7 +16,6 @@ import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; @@ -30,6 +29,7 @@ import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.Move; public class SelectorTestUtils { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index 00dca59ab0d..6cc6a34a47a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -13,7 +13,7 @@ import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.CachingMoveSelector; @@ -21,6 +21,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ShufflingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.SortingMoveSelector; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.common.DummyValueFactory; @@ -31,13 +32,13 @@ class MoveSelectorFactoryTest { @Test void phaseOriginal() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setCacheType(SelectionCacheType.PHASE); moveSelectorConfig.setSelectionOrder(SelectionOrder.ORIGINAL); - MoveSelectorFactory moveSelectorFactory = + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.PHASE, false); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(CachingMoveSelector.class) @@ -49,13 +50,13 @@ void phaseOriginal() { @Test void stepOriginal() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setCacheType(SelectionCacheType.STEP); moveSelectorConfig.setSelectionOrder(SelectionOrder.ORIGINAL); - MoveSelectorFactory moveSelectorFactory = + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.STEP, false); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(CachingMoveSelector.class) @@ -67,13 +68,13 @@ void stepOriginal() { @Test void justInTimeOriginal() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.JUST_IN_TIME, false); moveSelectorConfig.setCacheType(SelectionCacheType.JUST_IN_TIME); moveSelectorConfig.setSelectionOrder(SelectionOrder.ORIGINAL); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector).isSameAs(baseMoveSelector); assertThat(moveSelector.getCacheType()).isEqualTo(SelectionCacheType.JUST_IN_TIME); @@ -81,13 +82,13 @@ void justInTimeOriginal() { @Test void phaseRandom() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.PHASE, false); moveSelectorConfig.setCacheType(SelectionCacheType.PHASE); moveSelectorConfig.setSelectionOrder(SelectionOrder.RANDOM); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(CachingMoveSelector.class) @@ -99,13 +100,13 @@ void phaseRandom() { @Test void stepRandom() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.STEP, false); moveSelectorConfig.setCacheType(SelectionCacheType.STEP); moveSelectorConfig.setSelectionOrder(SelectionOrder.RANDOM); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(CachingMoveSelector.class) @@ -117,13 +118,13 @@ void stepRandom() { @Test void justInTimeRandom() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.JUST_IN_TIME, true); moveSelectorConfig.setCacheType(SelectionCacheType.JUST_IN_TIME); moveSelectorConfig.setSelectionOrder(SelectionOrder.RANDOM); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector).isSameAs(baseMoveSelector); assertThat(moveSelector.getCacheType()).isEqualTo(SelectionCacheType.JUST_IN_TIME); @@ -131,13 +132,13 @@ void justInTimeRandom() { @Test void phaseShuffled() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.PHASE, false); moveSelectorConfig.setCacheType(SelectionCacheType.PHASE); moveSelectorConfig.setSelectionOrder(SelectionOrder.SHUFFLED); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(ShufflingMoveSelector.class); @@ -148,28 +149,27 @@ void phaseShuffled() { @Test void stepShuffled() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new AssertingMoveSelectorFactory(moveSelectorConfig, baseMoveSelector, SelectionCacheType.STEP, false); moveSelectorConfig.setCacheType(SelectionCacheType.STEP); moveSelectorConfig.setSelectionOrder(SelectionOrder.SHUFFLED); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false); assertThat(moveSelector) .isInstanceOf(ShufflingMoveSelector.class); assertThat(moveSelector.getCacheType()).isEqualTo(SelectionCacheType.STEP); - assertThat(((ShufflingMoveSelector) moveSelector).getChildMoveSelector()).isSameAs(baseMoveSelector); + assertThat(((ShufflingMoveSelector) moveSelector).getChildMoveSelector()).isSameAs(baseMoveSelector); } @Test void justInTimeShuffled() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setCacheType(SelectionCacheType.JUST_IN_TIME); moveSelectorConfig.setSelectionOrder(SelectionOrder.SHUFFLED); - MoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, - baseMoveSelector); + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); assertThatIllegalArgumentException() .isThrownBy(() -> moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), @@ -178,22 +178,22 @@ void justInTimeShuffled() { @Test void validateSorting_incompatibleSelectionOrder() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); assertThatIllegalArgumentException().isThrownBy(() -> moveSelectorFactory.validateSorting(SelectionOrder.RANDOM)) .withMessageContaining("that is not " + SelectionOrder.SORTED); } @Test void applySorting_withoutAnySortingClass() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); assertThatIllegalArgumentException().isThrownBy( () -> moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector)) .withMessageContaining("The moveSelectorConfig") @@ -202,62 +202,61 @@ void applySorting_withoutAnySortingClass() { @Test void applySorting_withComparatorClass() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); moveSelectorConfig.setComparatorClass(DummyComparator.class); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var sortingMoveSelector = moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); } @Test void applySorting_withComparatorFactoryClass() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var sortingMoveSelector = moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); } @Test void applyProbability_withProbabilityWeightFactoryClass() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); + var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setCacheType(SelectionCacheType.PHASE); moveSelectorConfig.setProbabilityWeightFactoryClass(DummySelectionProbabilityWeightFactory.class); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var sortingMoveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, SelectionOrder.PROBABILISTIC, false); assertThat(sortingMoveSelector).isExactlyInstanceOf(ProbabilityMoveSelector.class); } @Test void applyFilter_nonMovableMoves() { - Move notDoableMove = new Move<>() { + var notDoableMove = new AbstractSelectorBasedMove() { @Override public boolean isMoveDoable(ScoreDirector scoreDirector) { return false; } @Override - public Move doMove(ScoreDirector scoreDirector) { - return null; + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + throw new IllegalStateException("This move should not be executed."); } + }; - final MoveSelector baseMoveSelector = - SelectorTestUtils.mockMoveSelector(notDoableMove); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - MoveSelectorFactory moveSelectorFactory = - new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), + var baseMoveSelector = SelectorTestUtils.mockMoveSelector(notDoableMove); + var moveSelectorConfig = new DummyMoveSelectorConfig(); + var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var moveSelector = moveSelectorFactory.buildMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, SelectionOrder.RANDOM, true); assertThat(moveSelector) .isInstanceOf(FilteringMoveSelector.class); @@ -284,7 +283,7 @@ public boolean hasNearbySelectionConfig() { static class DummyMoveSelectorFactory extends AbstractMoveSelectorFactory { - protected final MoveSelector baseMoveSelector; + protected MoveSelector baseMoveSelector; DummyMoveSelectorFactory(DummyMoveSelectorConfig moveSelectorConfig, MoveSelector baseMoveSelector) { @@ -293,7 +292,7 @@ static class DummyMoveSelectorFactory extends AbstractMoveSelectorFactory buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, + protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { return baseMoveSelector; @@ -314,7 +313,7 @@ static class AssertingMoveSelectorFactory extends DummyMoveSelectorFactory { } @Override - protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, + protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { assertThat(minimumCacheType).isEqualTo(expectedMinimumCacheType); assertThat(randomSelection).isEqualTo(expectedRandomSelection); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java index f0cde221813..043bf3743a1 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicRecordingEntitySelector; @@ -43,9 +43,9 @@ void originSelectionIgnoringEmpty() { public void originSelection(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, false); @@ -104,7 +104,7 @@ void emptyAllOriginSelectionIgnoringEmpty() { public void emptyOriginSelection(boolean ignoreEmptyChildIterators, boolean emptyFirst, boolean emptySecond) { assertThat(emptyFirst || emptySecond).isTrue(); MoveSelector nonEmptyChildMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3")); // One side is not empty + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3")); // One side is not empty ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(emptyFirst ? SelectorTestUtils.mockMoveSelector() @@ -151,11 +151,11 @@ void originSelection3ChildMoveSelectorsIgnoringEmpty() { public void originSelection3ChildMoveSelectors(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("c1"), new DummyMove("c2"))); + new SelectorBasedDummyMove("c1"), new SelectorBasedDummyMove("c2"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, false); @@ -193,10 +193,10 @@ void emptyOriginSelection3ChildMoveSelectorsIgnoringEmpty() { public void emptyOriginSelection3ChildMoveSelectors(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector()); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("c1"), new DummyMove("c2"))); + new SelectorBasedDummyMove("c1"), new SelectorBasedDummyMove("c2"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, false); @@ -237,9 +237,9 @@ void classicRandomSelectionIgnoringEmpty() { public void classicRandomSelection(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, true); @@ -276,7 +276,7 @@ public void emptyRandomSelection(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector()); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); // One side is not empty + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); // One side is not empty CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, true); @@ -316,11 +316,11 @@ void randomSelection3ChildMoveSelectorsIgnoringEmpty() { public void randomSelection3ChildMoveSelectors(boolean ignoreEmptyChildIterators) { ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("c1"), new DummyMove("c2"))); + new SelectorBasedDummyMove("c1"), new SelectorBasedDummyMove("c2"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, true); @@ -357,9 +357,9 @@ public void emptyRandomSelection3ChildMoveSelectors(boolean ignoreEmptyChildIter ArrayList childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector()); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("c1"), new DummyMove("c2"), new DummyMove("c3"))); + new SelectorBasedDummyMove("c1"), new SelectorBasedDummyMove("c2"), new SelectorBasedDummyMove("c3"))); CartesianProductMoveSelector moveSelector = new CartesianProductMoveSelector(childMoveSelectorList, ignoreEmptyChildIterators, true); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorTest.java index 1976ae42cf2..d1a301e26fc 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorTest.java @@ -13,7 +13,7 @@ import java.util.Random; import ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; @@ -31,9 +31,9 @@ class UnionMoveSelectorTest { void originSelection() { ArrayList> childMoveSelectorList = new ArrayList<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"))); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); UnionMoveSelector moveSelector = new UnionMoveSelector<>(childMoveSelectorList, false); @@ -88,10 +88,10 @@ void biasedRandomSelection() { ArrayList> childMoveSelectorList = new ArrayList<>(); Map, Double> fixedProbabilityWeightMap = new HashMap<>(); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"))); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"))); fixedProbabilityWeightMap.put(childMoveSelectorList.get(0), 1000.0); childMoveSelectorList.add(SelectorTestUtils.mockMoveSelector( - new DummyMove("b1"), new DummyMove("b2"))); + new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); fixedProbabilityWeightMap.put(childMoveSelectorList.get(1), 20.0); UnionMoveSelector moveSelector = new UnionMoveSelector<>(childMoveSelectorList, true, @@ -125,9 +125,9 @@ void biasedRandomSelection() { @Test void uniformRandomSelection() { List> childMoveSelectorList = List.of( - SelectorTestUtils.mockMoveSelector(new DummyMove("a1"), new DummyMove("a2"), - new DummyMove("a3")), - SelectorTestUtils.mockMoveSelector(new DummyMove("b1"), new DummyMove("b2"))); + SelectorTestUtils.mockMoveSelector(new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), + new SelectorBasedDummyMove("a3")), + SelectorTestUtils.mockMoveSelector(new SelectorBasedDummyMove("b1"), new SelectorBasedDummyMove("b2"))); UnionMoveSelector moveSelector = new UnionMoveSelector<>(childMoveSelectorList, true, null); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelectorTest.java index acd0675caf3..c70776256d2 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/CachingMoveSelectorTest.java @@ -9,7 +9,7 @@ import static org.mockito.Mockito.when; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; @@ -39,7 +39,7 @@ void originalSelectionCacheTypeStep() { public void runOriginalSelection(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3")); CachingMoveSelector moveSelector = new CachingMoveSelector(childMoveSelector, cacheType, false); verify(childMoveSelector, times(1)).isNeverEnding(); @@ -113,7 +113,7 @@ void randomSelectionCacheTypeStep() { public void runRandomSelection(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3")); CachingMoveSelector moveSelector = new CachingMoveSelector(childMoveSelector, cacheType, true); verify(childMoveSelector, times(1)).isNeverEnding(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelectorTest.java index 5d1cda3b1de..ea412456272 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelectorTest.java @@ -12,7 +12,7 @@ import java.util.Iterator; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -67,9 +67,11 @@ void bailOutByTermination() { public void filter(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"), new DummyMove("a4")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"), + new SelectorBasedDummyMove("a4")); - SelectionFilter filter = (scoreDirector, move) -> !move.getCode().equals("a3"); + SelectionFilter filter = + (scoreDirector, move) -> !move.getCode().equals("a3"); MoveSelector moveSelector = FilteringMoveSelector.of(childMoveSelector, (SelectionFilter) filter); if (cacheType.isCached()) { moveSelector = new CachingMoveSelector(moveSelector, cacheType, false); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelectorTest.java index 73c66a0941b..8d16512fe22 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelectorTest.java @@ -10,7 +10,7 @@ import java.util.Random; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -28,9 +28,10 @@ class ProbabilityMoveSelectorTest { @Test void randomSelection() { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("e1"), new DummyMove("e2"), new DummyMove("e3"), new DummyMove("e4")); + new SelectorBasedDummyMove("e1"), new SelectorBasedDummyMove("e2"), new SelectorBasedDummyMove("e3"), + new SelectorBasedDummyMove("e4")); - SelectionProbabilityWeightFactory probabilityWeightFactory = + SelectionProbabilityWeightFactory probabilityWeightFactory = (scoreDirector, move) -> { switch (move.getCode()) { case "e1": diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelectorTest.java index 80ab96e7022..b65a754a077 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SelectedCountLimitMoveSelectorTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; @@ -21,7 +21,8 @@ class SelectedCountLimitMoveSelectorTest { @Test void selectSizeLimitLowerThanSelectorSize() { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3"), new DummyMove("a4"), new DummyMove("a5")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3"), + new SelectorBasedDummyMove("a4"), new SelectorBasedDummyMove("a5")); MoveSelector moveSelector = new SelectedCountLimitMoveSelector(childMoveSelector, 3L); SolverScope solverScope = mock(SolverScope.class); @@ -79,7 +80,7 @@ void selectSizeLimitLowerThanSelectorSize() { @Test void selectSizeLimitHigherThanSelectorSize() { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3")); MoveSelector moveSelector = new SelectedCountLimitMoveSelector(childMoveSelector, 5L); SolverScope solverScope = mock(SolverScope.class); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelectorTest.java index a7775129852..7505daab8d5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ShufflingMoveSelectorTest.java @@ -8,7 +8,7 @@ import static org.mockito.Mockito.when; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; @@ -38,7 +38,7 @@ void cacheTypeStep() { public void run(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("a1"), new DummyMove("a2"), new DummyMove("a3")); + new SelectorBasedDummyMove("a1"), new SelectorBasedDummyMove("a2"), new SelectorBasedDummyMove("a3")); ShufflingMoveSelector moveSelector = new ShufflingMoveSelector(childMoveSelector, cacheType); verify(childMoveSelector, times(1)).isNeverEnding(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index 6131a09b622..01b8a00f57c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -20,7 +20,7 @@ import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.CodeAssertableSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; @@ -80,8 +80,8 @@ private static List generateConfiguration() { @MethodSource("generateConfiguration") void applySorting(DummySorterMoveSelectorConfig moveSelectorConfig) { var baseMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("jan"), new DummyMove("feb"), new DummyMove("mar"), - new DummyMove("apr"), new DummyMove("may"), new DummyMove("jun")); + new SelectorBasedDummyMove("jan"), new SelectorBasedDummyMove("feb"), new SelectorBasedDummyMove("mar"), + new SelectorBasedDummyMove("apr"), new SelectorBasedDummyMove("may"), new SelectorBasedDummyMove("jun")); var moveSelectorFactory = new DummySorterMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); var moveSelector = moveSelectorFactory.buildBaseMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, false); @@ -103,11 +103,11 @@ void applySorting(DummySorterMoveSelectorConfig moveSelectorConfig) { public void runCacheType(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( - new DummyMove("jan"), new DummyMove("feb"), new DummyMove("mar"), - new DummyMove("apr"), new DummyMove("may"), new DummyMove("jun")); + new SelectorBasedDummyMove("jan"), new SelectorBasedDummyMove("feb"), new SelectorBasedDummyMove("mar"), + new SelectorBasedDummyMove("apr"), new SelectorBasedDummyMove("may"), new SelectorBasedDummyMove("jun")); MoveSelector moveSelector = - new SortingMoveSelector(childMoveSelector, cacheType, new CodeAssertableSorter()); + new SortingMoveSelector(childMoveSelector, cacheType, new CodeAssertableSorter()); SolverScope solverScope = mock(SolverScope.class); InnerScoreDirector scoreDirector = mock(InnerScoreDirector.class); 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/SelectorBasedChangeMoveTest.java similarity index 69% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMoveTest.java index 96ee0be9639..7c93eef1586 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/SelectorBasedChangeMoveTest.java @@ -11,6 +11,7 @@ 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.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -22,7 +23,7 @@ import org.junit.jupiter.api.Test; -class ChangeMoveTest { +class SelectorBasedChangeMoveTest { @Test void isMoveDoable() { @@ -36,7 +37,7 @@ void isMoveDoable() { var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); - var aMove = new ChangeMove<>(variableDescriptor, a, v2); + var aMove = new SelectorBasedChangeMove<>(variableDescriptor, a, v2); a.setValue(v1); assertThat(aMove.isMoveDoable(scoreDirector)).isTrue(); @@ -61,17 +62,17 @@ void doMove() { var scoreDirector = scoreDirectorFactory.buildScoreDirector(); var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); - var aMove = new ChangeMove<>(variableDescriptor, a, v2); + var aMove = new SelectorBasedChangeMove<>(variableDescriptor, a, v2); a.setValue(v1); - aMove.doMoveOnly(scoreDirector); + aMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); a.setValue(v2); - aMove.doMoveOnly(scoreDirector); + aMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); a.setValue(v3); - aMove.doMoveOnly(scoreDirector); + aMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); } @@ -91,7 +92,7 @@ void rebase() { var destinationE2 = new TestdataEntity("e2", null); var destinationE3 = new TestdataEntity("e3", destinationV1); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -101,16 +102,16 @@ void rebase() { }); assertSameProperties(destinationE1, null, - new ChangeMove<>(variableDescriptor, e1, null).rebase(destinationScoreDirector)); + new SelectorBasedChangeMove<>(variableDescriptor, e1, null).rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(destinationE1, destinationV1, - new ChangeMove<>(variableDescriptor, e1, v1).rebase(destinationScoreDirector)); + new SelectorBasedChangeMove<>(variableDescriptor, e1, v1).rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(destinationE2, null, - new ChangeMove<>(variableDescriptor, e2, null).rebase(destinationScoreDirector)); + new SelectorBasedChangeMove<>(variableDescriptor, e2, null).rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(destinationE3, destinationV2, - new ChangeMove<>(variableDescriptor, e3, v2).rebase(destinationScoreDirector)); + new SelectorBasedChangeMove<>(variableDescriptor, e3, v2).rebase(destinationScoreDirector.getMoveDirector())); } - public void assertSameProperties(Object entity, Object toPlanningVariable, ChangeMove move) { + public void assertSameProperties(Object entity, Object toPlanningVariable, SelectorBasedChangeMove move) { assertThat(move.getEntity()).isSameAs(entity); assertThat(move.getToPlanningValue()).isSameAs(toPlanningVariable); } @@ -119,14 +120,15 @@ public void assertSameProperties(Object entity, Object toPlanningVariable, Chang void getters() { var primaryVariableDescriptor = TestdataMultiVarEntity.buildVariableDescriptorForPrimaryValue(); - var primaryMove = new ChangeMove<>(primaryVariableDescriptor, new TestdataMultiVarEntity("a"), null); + var primaryMove = new SelectorBasedChangeMove<>(primaryVariableDescriptor, new TestdataMultiVarEntity("a"), null); assertCode("a", primaryMove.getEntity()); assertThat(primaryMove.getVariableName()).isEqualTo("primaryValue"); assertCode(null, primaryMove.getToPlanningValue()); var secondaryVariableDescriptor = TestdataMultiVarEntity.buildVariableDescriptorForSecondaryValue(); var secondaryMove = - new ChangeMove<>(secondaryVariableDescriptor, new TestdataMultiVarEntity("b"), new TestdataValue("1")); + new SelectorBasedChangeMove<>(secondaryVariableDescriptor, new TestdataMultiVarEntity("b"), + new TestdataValue("1")); assertCode("b", secondaryMove.getEntity()); assertThat(secondaryMove.getVariableName()).isEqualTo("secondaryValue"); assertCode("1", secondaryMove.getToPlanningValue()); @@ -140,12 +142,12 @@ void toStringTest() { var b = new TestdataEntity("b", v1); var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); - assertThat(new ChangeMove<>(variableDescriptor, a, null)).hasToString("a {null -> null}"); - assertThat(new ChangeMove<>(variableDescriptor, a, v1)).hasToString("a {null -> v1}"); - assertThat(new ChangeMove<>(variableDescriptor, a, v2)).hasToString("a {null -> v2}"); - assertThat(new ChangeMove<>(variableDescriptor, b, null)).hasToString("b {v1 -> null}"); - assertThat(new ChangeMove<>(variableDescriptor, b, v1)).hasToString("b {v1 -> v1}"); - assertThat(new ChangeMove<>(variableDescriptor, b, v2)).hasToString("b {v1 -> v2}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, null)).hasToString("a {null -> null}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, v1)).hasToString("a {null -> v1}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, v2)).hasToString("a {null -> v2}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, null)).hasToString("b {v1 -> null}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, v1)).hasToString("b {v1 -> v1}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, v2)).hasToString("b {v1 -> v2}"); } @Test @@ -162,13 +164,13 @@ void toStringTestMultiVar() { var variableDescriptor = TestdataMultiVarEntity.buildVariableDescriptorForSecondaryValue(); - assertThat(new ChangeMove<>(variableDescriptor, a, null)).hasToString("a {null -> null}"); - assertThat(new ChangeMove<>(variableDescriptor, a, v1)).hasToString("a {null -> v1}"); - assertThat(new ChangeMove<>(variableDescriptor, a, v2)).hasToString("a {null -> v2}"); - assertThat(new ChangeMove<>(variableDescriptor, b, null)).hasToString("b {v3 -> null}"); - assertThat(new ChangeMove<>(variableDescriptor, b, v1)).hasToString("b {v3 -> v1}"); - assertThat(new ChangeMove<>(variableDescriptor, b, v2)).hasToString("b {v3 -> v2}"); - assertThat(new ChangeMove<>(variableDescriptor, c, v3)).hasToString("c {v4 -> v3}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, null)).hasToString("a {null -> null}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, v1)).hasToString("a {null -> v1}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, a, v2)).hasToString("a {null -> v2}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, null)).hasToString("b {v3 -> null}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, v1)).hasToString("b {v3 -> v1}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, b, v2)).hasToString("b {v3 -> v2}"); + assertThat(new SelectorBasedChangeMove<>(variableDescriptor, c, v3)).hasToString("c {v4 -> v3}"); } @Test 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/SelectorBasedPillarChangeMoveTest.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMoveTest.java index 3c19e3dc166..d70a46dbddf 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/SelectorBasedPillarChangeMoveTest.java @@ -11,8 +11,8 @@ import java.util.List; import ai.timefold.solver.core.api.score.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.InnerScoreDirector; 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; @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; -class PillarChangeMoveTest { +class SelectorBasedPillarChangeMoveTest { @Test void isMoveDoableValueRangeProviderOnEntity() { @@ -48,22 +48,21 @@ void isMoveDoableValueRangeProviderOnEntity() { var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); - PillarChangeMove abMove; a.setValue(v2); b.setValue(v2); - abMove = new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v1); + var abMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v1); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); a.setValue(v2); b.setValue(v2); - abMove = new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2); + abMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); a.setValue(v2); b.setValue(v2); - abMove = new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v3); + abMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v3); assertThat(abMove.isMoveDoable(scoreDirector)).isTrue(); a.setValue(v2); b.setValue(v2); - abMove = new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v4); + abMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v4); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); } @@ -82,31 +81,31 @@ void doMove() { var scoreDirectorFactory = new EasyScoreDirectorFactory<>(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(), solution -> SimpleScore.ZERO, EnvironmentMode.PHASE_ASSERT); - ScoreDirector scoreDirector = + InnerScoreDirector scoreDirector = scoreDirectorFactory.buildScoreDirector(); var variableDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildVariableDescriptorForValue(); - var abMove = new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2); + var abMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2); a.setValue(v3); b.setValue(v3); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v2); a.setValue(v2); b.setValue(v2); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v2); - var abcMove = new PillarChangeMove<>(Arrays.asList(a, b, c), + var abcMove = new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b, c), variableDescriptor, v2); a.setValue(v2); b.setValue(v2); c.setValue(v2); - abcMove.doMoveOnly(scoreDirector); + abcMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v2); @@ -114,7 +113,7 @@ void doMove() { a.setValue(v3); b.setValue(v3); c.setValue(v3); - abcMove.doMoveOnly(scoreDirector); + abcMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v2); @@ -140,7 +139,7 @@ void rebase() { var destinationE3 = new TestdataEntity("e3", destinationV1); var destinationE4 = new TestdataEntity("e4", destinationV3); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -152,30 +151,34 @@ void rebase() { }); assertSameProperties(Arrays.asList(destinationE1, destinationE3), null, - new PillarChangeMove<>(Arrays.asList(e1, e3), variableDescriptor, null).rebase(destinationScoreDirector)); + new SelectorBasedPillarChangeMove<>(Arrays.asList(e1, e3), variableDescriptor, null) + .rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(Arrays.asList(destinationE1, destinationE3), destinationV3, - new PillarChangeMove<>(Arrays.asList(e1, e3), variableDescriptor, v3).rebase(destinationScoreDirector)); + new SelectorBasedPillarChangeMove<>(Arrays.asList(e1, e3), variableDescriptor, v3) + .rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(Arrays.asList(destinationE2), destinationV1, - new PillarChangeMove<>(Arrays.asList(e2), variableDescriptor, v1).rebase(destinationScoreDirector)); + new SelectorBasedPillarChangeMove<>(Arrays.asList(e2), variableDescriptor, v1) + .rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(Arrays.asList(destinationE1), destinationV2, - new PillarChangeMove<>(Arrays.asList(e1), variableDescriptor, v2).rebase(destinationScoreDirector)); + new SelectorBasedPillarChangeMove<>(Arrays.asList(e1), variableDescriptor, v2) + .rebase(destinationScoreDirector.getMoveDirector())); } - public void assertSameProperties(List pillar, Object toPlanningVariable, PillarChangeMove move) { + public void assertSameProperties(List pillar, Object toPlanningVariable, SelectorBasedPillarChangeMove move) { assertThat(move.getPillar()).hasSameElementsAs(pillar); assertThat(move.getToPlanningValue()).isSameAs(toPlanningVariable); } @Test void getters() { - var move = new PillarChangeMove<>( + var move = new SelectorBasedPillarChangeMove<>( Arrays.asList(new TestdataMultiVarEntity("a"), new TestdataMultiVarEntity("b")), TestdataMultiVarEntity.buildVariableDescriptorForPrimaryValue(), null); assertAllCodesOfCollection(move.getPillar(), "a", "b"); assertThat(move.getVariableName()).isEqualTo("primaryValue"); assertCode(null, move.getToPlanningValue()); - move = new PillarChangeMove<>( + move = new SelectorBasedPillarChangeMove<>( Arrays.asList(new TestdataMultiVarEntity("c"), new TestdataMultiVarEntity("d")), TestdataMultiVarEntity.buildVariableDescriptorForSecondaryValue(), new TestdataValue("1")); assertAllCodesOfCollection(move.getPillar(), "c", "d"); @@ -194,12 +197,15 @@ void toStringTest() { var e = new TestdataEntity("e", v1); var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue(); - assertThat(new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v1)).hasToString("[a, b] {null -> v1}"); - assertThat(new PillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2)).hasToString("[a, b] {null -> v2}"); - assertThat(new PillarChangeMove<>(Arrays.asList(c, d, e), variableDescriptor, null)) + assertThat(new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v1)) + .hasToString("[a, b] {null -> v1}"); + assertThat(new SelectorBasedPillarChangeMove<>(Arrays.asList(a, b), variableDescriptor, v2)) + .hasToString("[a, b] {null -> v2}"); + assertThat(new SelectorBasedPillarChangeMove<>(Arrays.asList(c, d, e), variableDescriptor, null)) .hasToString("[c, d, e] {v1 -> null}"); - assertThat(new PillarChangeMove<>(Arrays.asList(c, d, e), variableDescriptor, v2)).hasToString("[c, d, e] {v1 -> v2}"); - assertThat(new PillarChangeMove<>(Arrays.asList(d), variableDescriptor, v2)).hasToString("[d] {v1 -> v2}"); + assertThat(new SelectorBasedPillarChangeMove<>(Arrays.asList(c, d, e), variableDescriptor, v2)) + .hasToString("[c, d, e] {v1 -> v2}"); + assertThat(new SelectorBasedPillarChangeMove<>(Arrays.asList(d), variableDescriptor, v2)).hasToString("[d] {v1 -> v2}"); } } 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/SelectorBasedPillarSwapMoveTest.java similarity index 81% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMoveTest.java index b1abf3fb9b5..cea600251cc 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/SelectorBasedPillarSwapMoveTest.java @@ -7,11 +7,12 @@ import static org.mockito.Mockito.mock; import java.util.Arrays; +import java.util.Collections; import java.util.List; import ai.timefold.solver.core.api.score.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.InnerScoreDirector; 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; @@ -24,7 +25,7 @@ import org.junit.jupiter.api.Test; -class PillarSwapMoveTest { +class SelectorBasedPillarSwapMoveTest { @Test void isMoveDoableValueRangeProviderOnEntity() { @@ -50,7 +51,7 @@ void isMoveDoableValueRangeProviderOnEntity() { var variableDescriptorList = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor().getGenuineVariableDescriptorList(); - var abMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a), Arrays.asList(b)); + var abMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(a), List.of(b)); a.setValue(v1); b.setValue(v2); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); @@ -70,7 +71,7 @@ void isMoveDoableValueRangeProviderOnEntity() { b.setValue(v4); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); - var acMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a), Arrays.asList(c)); + var acMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(a), List.of(c)); a.setValue(v1); c.setValue(v4); assertThat(acMove.isMoveDoable(scoreDirector)).isFalse(); @@ -78,7 +79,7 @@ void isMoveDoableValueRangeProviderOnEntity() { c.setValue(v5); assertThat(acMove.isMoveDoable(scoreDirector)).isFalse(); - var bcMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(b), Arrays.asList(c)); + var bcMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(b), List.of(c)); b.setValue(v2); c.setValue(v4); assertThat(bcMove.isMoveDoable(scoreDirector)).isFalse(); @@ -92,7 +93,7 @@ void isMoveDoableValueRangeProviderOnEntity() { c.setValue(v5); assertThat(bcMove.isMoveDoable(scoreDirector)).isFalse(); - var abzMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(z)); + var abzMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), List.of(z)); a.setValue(v2); b.setValue(v2); z.setValue(v4); @@ -135,39 +136,39 @@ void doMove() { var variableDescriptorList = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor().getGenuineVariableDescriptorList(); - var abMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a), Arrays.asList(b)); + var abMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(a), List.of(b)); a.setValue(v1); b.setValue(v1); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v1); assertThat(b.getValue()).isEqualTo(v1); a.setValue(v2); b.setValue(v1); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v1); assertThat(b.getValue()).isEqualTo(v2); a.setValue(v3); b.setValue(v2); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v3); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v2); - var abzMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(z)); + var abzMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), List.of(z)); a.setValue(v3); b.setValue(v3); z.setValue(v2); - abzMove.doMoveOnly(scoreDirector); + abzMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v2); assertThat(z.getValue()).isEqualTo(v3); - abzMove.doMoveOnly(scoreDirector); + abzMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v3); assertThat(z.getValue()).isEqualTo(v2); @@ -175,44 +176,44 @@ void doMove() { a.setValue(v3); b.setValue(v3); z.setValue(v4); - abzMove.doMoveOnly(scoreDirector); + abzMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v4); assertThat(b.getValue()).isEqualTo(v4); assertThat(z.getValue()).isEqualTo(v3); - abzMove.doMoveOnly(scoreDirector); + abzMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v3); assertThat(z.getValue()).isEqualTo(v4); - var abczMove = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a), Arrays.asList(b, c, z)); + var abczMove = new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(a), Arrays.asList(b, c, z)); a.setValue(v2); b.setValue(v3); c.setValue(v3); z.setValue(v3); - abczMove.doMoveOnly(scoreDirector); + abczMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v2); assertThat(z.getValue()).isEqualTo(v2); - abczMove.doMoveOnly(scoreDirector); + abczMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v3); assertThat(c.getValue()).isEqualTo(v3); assertThat(z.getValue()).isEqualTo(v3); - var abczMove2 = new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(c, z)); + var abczMove2 = new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(c, z)); a.setValue(v4); b.setValue(v4); c.setValue(v3); z.setValue(v3); - abczMove2.doMoveOnly(scoreDirector); + abczMove2.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v3); assertThat(c.getValue()).isEqualTo(v4); assertThat(z.getValue()).isEqualTo(v4); - abczMove2.doMoveOnly(scoreDirector); + abczMove2.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v4); assertThat(b.getValue()).isEqualTo(v4); assertThat(c.getValue()).isEqualTo(v3); @@ -241,7 +242,7 @@ void rebase() { var destinationE3 = new TestdataEntity("e3", destinationV1); var destinationE4 = new TestdataEntity("e4", destinationV3); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( entityDescriptor.getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -252,15 +253,15 @@ void rebase() { { e4, destinationE4 }, }); - assertSameProperties(Arrays.asList(destinationE1, destinationE3), Arrays.asList(destinationE2), - new PillarSwapMove<>(variableDescriptorList, Arrays.asList(e1, e3), Arrays.asList(e2)) - .rebase(destinationScoreDirector)); - assertSameProperties(Arrays.asList(destinationE4), Arrays.asList(destinationE1, destinationE3), - new PillarSwapMove<>(variableDescriptorList, Arrays.asList(e4), Arrays.asList(e1, e3)) - .rebase(destinationScoreDirector)); + assertSameProperties(Arrays.asList(destinationE1, destinationE3), List.of(destinationE2), + new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(e1, e3), List.of(e2)) + .rebase(destinationScoreDirector.getMoveDirector())); + assertSameProperties(List.of(destinationE4), Arrays.asList(destinationE1, destinationE3), + new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(e4), Arrays.asList(e1, e3)) + .rebase(destinationScoreDirector.getMoveDirector())); } - public void assertSameProperties(List leftPillar, List rightPillar, PillarSwapMove move) { + public void assertSameProperties(List leftPillar, List rightPillar, SelectorBasedPillarSwapMove move) { assertThat(move.getLeftPillar()).hasSameElementsAs(leftPillar); assertThat(move.getRightPillar()).hasSameElementsAs(rightPillar); } @@ -271,14 +272,14 @@ void getters() { .buildVariableDescriptorForPrimaryValue(); var secondaryDescriptor = TestdataMultiVarEntity .buildVariableDescriptorForSecondaryValue(); - var move = new PillarSwapMove<>(Arrays.asList(primaryDescriptor), + var move = new SelectorBasedPillarSwapMove<>(Collections.singletonList(primaryDescriptor), Arrays.asList(new TestdataMultiVarEntity("a"), new TestdataMultiVarEntity("b")), Arrays.asList(new TestdataMultiVarEntity("c"), new TestdataMultiVarEntity("d"))); assertThat(move.getVariableNameList()).containsExactly("primaryValue"); assertAllCodesOfCollection(move.getLeftPillar(), "a", "b"); assertAllCodesOfCollection(move.getRightPillar(), "c", "d"); - move = new PillarSwapMove<>(Arrays.asList(primaryDescriptor, secondaryDescriptor), + move = new SelectorBasedPillarSwapMove<>(Arrays.asList(primaryDescriptor, secondaryDescriptor), Arrays.asList(new TestdataMultiVarEntity("e"), new TestdataMultiVarEntity("f")), Arrays.asList(new TestdataMultiVarEntity("g"), new TestdataMultiVarEntity("h"))); assertThat(move.getVariableNameList()).containsExactly("primaryValue", "secondaryValue"); @@ -300,11 +301,11 @@ void toStringTest() { var variableDescriptorList = TestdataEntity.buildEntityDescriptor() .getGenuineVariableDescriptorList(); - assertThat(new PillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(c, d, e))) + assertThat(new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(a, b), Arrays.asList(c, d, e))) .hasToString("[a, b] {null} <-> [c, d, e] {v1}"); - assertThat(new PillarSwapMove<>(variableDescriptorList, Arrays.asList(b), Arrays.asList(c))) + assertThat(new SelectorBasedPillarSwapMove<>(variableDescriptorList, List.of(b), List.of(c))) .hasToString("[b] {null} <-> [c] {v1}"); - assertThat(new PillarSwapMove<>(variableDescriptorList, Arrays.asList(f, g), Arrays.asList(c, d, e))) + assertThat(new SelectorBasedPillarSwapMove<>(variableDescriptorList, Arrays.asList(f, g), Arrays.asList(c, d, e))) .hasToString("[f, g] {v2} <-> [c, d, e] {v1}"); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMoveTest.java similarity index 73% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMoveTest.java index f30f37ad6c8..2850dd54bf2 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMoveTest.java @@ -6,7 +6,7 @@ import static org.mockito.Mockito.mock; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test; -class RuinRecreateMoveTest { +class SelectorBasedRuinRecreateMoveTest { @SuppressWarnings({ "unchecked", "rawtypes" }) @Test @@ -46,15 +46,15 @@ void rebase() { { e3, destinationE3 }, }); - var move = new RuinRecreateMove(mock(GenuineVariableDescriptor.class), + var move = new SelectorBasedRuinRecreateMove(mock(GenuineVariableDescriptor.class), mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), Arrays.asList(e1, e2, e3), - Set.of(v1, v2)); - var rebasedMove = move.rebase(destinationScoreDirector); + new LinkedHashSet(Set.of(v1, v2))); + var rebasedMove = move.rebase(destinationScoreDirector.getMoveDirector()); assertSoftly(softly -> { - softly.assertThat((Collection) rebasedMove.getPlanningEntities()) + softly.assertThat(rebasedMove.getPlanningEntities()) .containsExactly(destinationE1, destinationE2, destinationE3); - softly.assertThat((Collection) rebasedMove.getPlanningValues()) + softly.assertThat(rebasedMove.getPlanningValues()) .containsExactlyInAnyOrder(destinationV1, destinationV2); // The input set is not ordered. }); @@ -69,27 +69,27 @@ void equality() { var e2 = new TestdataEntity("e2", null); var descriptor = mock(GenuineVariableDescriptor.class); - var move = new RuinRecreateMove(descriptor, + var move = new SelectorBasedRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); - var sameMove = new RuinRecreateMove(descriptor, + new LinkedHashSet<>(Set.of(v1))); + var sameMove = new SelectorBasedRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isEqualTo(sameMove); - var differentMove = new RuinRecreateMove(descriptor, + var differentMove = new SelectorBasedRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v2)); + new LinkedHashSet<>(Set.of(v2))); assertThat(move).isNotEqualTo(differentMove); - var anotherDifferentMove = new RuinRecreateMove(descriptor, + var anotherDifferentMove = new SelectorBasedRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e2), - Set.of(v1)); + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isNotEqualTo(anotherDifferentMove); - var yetAnotherDifferentMove = new RuinRecreateMove(mock(GenuineVariableDescriptor.class), + var yetAnotherDifferentMove = new SelectorBasedRuinRecreateMove(mock(GenuineVariableDescriptor.class), mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isNotEqualTo(yetAnotherDifferentMove); } } 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/SelectorBasedSwapMoveTest.java similarity index 74% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMoveTest.java index 6200d30eb5d..7ec80f5b0bc 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/SelectorBasedSwapMoveTest.java @@ -10,9 +10,9 @@ import java.util.Collections; import ai.timefold.solver.core.api.score.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.InnerScoreDirector; 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; @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; -class SwapMoveTest { +class SelectorBasedSwapMoveTest { @Test void isMoveDoableValueRangeProviderOnEntity() { @@ -50,7 +50,7 @@ void isMoveDoableValueRangeProviderOnEntity() { var entityDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor(); - var abMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, b); + var abMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, b); a.setValue(v2); b.setValue(v3); assertThat(abMove.isMoveDoable(scoreDirector)).isTrue(); @@ -61,7 +61,7 @@ void isMoveDoableValueRangeProviderOnEntity() { b.setValue(v3); assertThat(abMove.isMoveDoable(scoreDirector)).isFalse(); - var bcMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), b, c); + var bcMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), b, c); b.setValue(v4); c.setValue(v5); assertThat(bcMove.isMoveDoable(scoreDirector)).isTrue(); @@ -72,7 +72,7 @@ void isMoveDoableValueRangeProviderOnEntity() { c.setValue(v5); assertThat(bcMove.isMoveDoable(scoreDirector)).isFalse(); - var aaMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, a); + var aaMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, a); assertThat(aaMove.isMoveDoable(scoreDirector)).isFalse(); } @@ -93,72 +93,72 @@ void doMove() { var scoreDirector = scoreDirectorFactory.buildScoreDirector(); var entityDescriptor = TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor(); - var abMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, b); + var abMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, b); a.setValue(v1); b.setValue(v1); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v1); assertThat(b.getValue()).isEqualTo(v1); a.setValue(v1); b.setValue(v2); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v1); a.setValue(v2); b.setValue(v3); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(b.getValue()).isEqualTo(v2); - abMove.doMoveOnly(scoreDirector); + abMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(b.getValue()).isEqualTo(v3); - var acMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, c); + var acMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), a, c); a.setValue(v2); c.setValue(v2); - acMove.doMoveOnly(scoreDirector); + acMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v2); a.setValue(v3); c.setValue(v2); - acMove.doMoveOnly(scoreDirector); + acMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v3); a.setValue(v3); c.setValue(v4); - acMove.doMoveOnly(scoreDirector); + acMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v4); assertThat(c.getValue()).isEqualTo(v3); - acMove.doMoveOnly(scoreDirector); + acMove.execute(scoreDirector); assertThat(a.getValue()).isEqualTo(v3); assertThat(c.getValue()).isEqualTo(v4); - var bcMove = new SwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), b, c); + var bcMove = new SelectorBasedSwapMove<>(entityDescriptor.getGenuineVariableDescriptorList(), b, c); b.setValue(v2); c.setValue(v2); - bcMove.doMoveOnly(scoreDirector); + bcMove.execute(scoreDirector); assertThat(b.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v2); b.setValue(v2); c.setValue(v3); - bcMove.doMoveOnly(scoreDirector); + bcMove.execute(scoreDirector); assertThat(b.getValue()).isEqualTo(v3); assertThat(c.getValue()).isEqualTo(v2); b.setValue(v2); c.setValue(v3); - bcMove.doMoveOnly(scoreDirector); + bcMove.execute(scoreDirector); assertThat(b.getValue()).isEqualTo(v3); assertThat(c.getValue()).isEqualTo(v2); - bcMove.doMoveOnly(scoreDirector); + bcMove.execute(scoreDirector); assertThat(b.getValue()).isEqualTo(v2); assertThat(c.getValue()).isEqualTo(v3); } @@ -180,7 +180,7 @@ void rebase() { var destinationE2 = new TestdataEntity("e2", null); var destinationE3 = new TestdataEntity("e3", destinationV1); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( entityDescriptor.getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -190,14 +190,14 @@ void rebase() { }); assertSameProperties(destinationE1, destinationE2, - new SwapMove<>(variableDescriptorList, e1, e2).rebase(destinationScoreDirector)); + new SelectorBasedSwapMove<>(variableDescriptorList, e1, e2).rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(destinationE1, destinationE3, - new SwapMove<>(variableDescriptorList, e1, e3).rebase(destinationScoreDirector)); + new SelectorBasedSwapMove<>(variableDescriptorList, e1, e3).rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties(destinationE2, destinationE3, - new SwapMove<>(variableDescriptorList, e2, e3).rebase(destinationScoreDirector)); + new SelectorBasedSwapMove<>(variableDescriptorList, e2, e3).rebase(destinationScoreDirector.getMoveDirector())); } - public void assertSameProperties(Object leftEntity, Object rightEntity, SwapMove move) { + public void assertSameProperties(Object leftEntity, Object rightEntity, SelectorBasedSwapMove move) { assertThat(move.getLeftEntity()).isSameAs(leftEntity); assertThat(move.getRightEntity()).isSameAs(rightEntity); } @@ -206,12 +206,12 @@ public void assertSameProperties(Object leftEntity, Object rightEntity, SwapMove void getters() { var primaryDescriptor = TestdataMultiVarEntity.buildVariableDescriptorForPrimaryValue(); var secondaryDescriptor = TestdataMultiVarEntity.buildVariableDescriptorForSecondaryValue(); - var move = new SwapMove<>(Collections.singletonList(primaryDescriptor), + var move = new SelectorBasedSwapMove<>(Collections.singletonList(primaryDescriptor), new TestdataMultiVarEntity("a"), new TestdataMultiVarEntity("b")); assertCode("a", move.getLeftEntity()); assertCode("b", move.getRightEntity()); - move = new SwapMove<>(Arrays.asList(primaryDescriptor, secondaryDescriptor), + move = new SelectorBasedSwapMove<>(Arrays.asList(primaryDescriptor, secondaryDescriptor), new TestdataMultiVarEntity("c"), new TestdataMultiVarEntity("d")); assertCode("c", move.getLeftEntity()); assertCode("d", move.getRightEntity()); @@ -227,11 +227,11 @@ void toStringTest() { var entityDescriptor = TestdataEntity.buildEntityDescriptor(); var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList(); - assertThat(new SwapMove<>(variableDescriptorList, a, a)).hasToString("a {null} <-> a {null}"); - assertThat(new SwapMove<>(variableDescriptorList, a, b)).hasToString("a {null} <-> b {v1}"); - assertThat(new SwapMove<>(variableDescriptorList, a, c)).hasToString("a {null} <-> c {v2}"); - assertThat(new SwapMove<>(variableDescriptorList, b, c)).hasToString("b {v1} <-> c {v2}"); - assertThat(new SwapMove<>(variableDescriptorList, c, b)).hasToString("c {v2} <-> b {v1}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, a)).hasToString("a {null} <-> a {null}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, b)).hasToString("a {null} <-> b {v1}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, c)).hasToString("a {null} <-> c {v2}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, b, c)).hasToString("b {v1} <-> c {v2}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, c, b)).hasToString("c {v2} <-> b {v1}"); } @Test @@ -248,11 +248,14 @@ void toStringTestMultiVar() { var entityDescriptor = TestdataMultiVarEntity.buildEntityDescriptor(); var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList(); - assertThat(new SwapMove<>(variableDescriptorList, a, a)).hasToString("a {null, null, null} <-> a {null, null, null}"); - assertThat(new SwapMove<>(variableDescriptorList, a, b)).hasToString("a {null, null, null} <-> b {v1, v3, w1}"); - assertThat(new SwapMove<>(variableDescriptorList, a, c)).hasToString("a {null, null, null} <-> c {v2, v4, w2}"); - assertThat(new SwapMove<>(variableDescriptorList, b, c)).hasToString("b {v1, v3, w1} <-> c {v2, v4, w2}"); - assertThat(new SwapMove<>(variableDescriptorList, c, b)).hasToString("c {v2, v4, w2} <-> b {v1, v3, w1}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, a)) + .hasToString("a {null, null, null} <-> a {null, null, null}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, b)) + .hasToString("a {null, null, null} <-> b {v1, v3, w1}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, a, c)) + .hasToString("a {null, null, null} <-> c {v2, v4, w2}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, b, c)).hasToString("b {v1, v3, w1} <-> c {v2, v4, w2}"); + assertThat(new SelectorBasedSwapMove<>(variableDescriptorList, c, b)).hasToString("c {v2, v4, w2} <-> b {v1, v3, w1}"); } @Test diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java index 07caea76455..a350b3b4b12 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java @@ -614,7 +614,7 @@ void noReachableEntities() { // The iterator is not able to find a reachable entity, but the random iterator will return has next as true var iterator = moveSelector.iterator(); assertThat(iterator.hasNext()).isTrue(); - var swapMove = (SwapMove) iterator.next(); + var swapMove = (SelectorBasedSwapMove) iterator.next(); assertThat(swapMove.getLeftEntity()).isSameAs(swapMove.getRightEntity()); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorTest.java index b3c9b27fb89..6516a77331b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorTest.java @@ -21,7 +21,7 @@ import java.util.Random; import ai.timefold.solver.core.api.solver.SolutionManager; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; @@ -577,6 +577,6 @@ void noReachableEntities() { // The iterator is not able to find a reachable entity, but the random iterator will return has next as true var iterator = moveSelector.iterator(); assertThat(iterator.hasNext()).isTrue(); - assertThat(iterator.next()).isSameAs(NoChangeMove.getInstance()); + assertThat(iterator.next()).isSameAs(SelectorBasedNoChangeMove.getInstance()); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMoveTest.java similarity index 82% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMoveTest.java index 52cb8457002..3750cdd177c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListAssignMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMoveTest.java @@ -9,7 +9,6 @@ import java.util.List; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.move.MoveDirector; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; @@ -24,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ListAssignMoveTest { +class SelectorBasedListAssignMoveTest { private final InnerScoreDirector innerScoreDirector = mock(InnerScoreDirector.class); private final MoveDirector moveDirector = new MoveDirector<>(innerScoreDirector); @@ -51,7 +50,7 @@ void doMove() { var v3 = new TestdataListValue("3"); var e1 = new TestdataListEntity("e1"); - moveDirector.executeTemporary(new ListAssignMove<>(variableDescriptor, v1, e1, 0), + moveDirector.executeTemporary(new SelectorBasedListAssignMove<>(variableDescriptor, v1, e1, 0), (__, ___) -> { assertThat(e1.getValueList()).containsExactly(v1); verify(innerScoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 0); @@ -63,11 +62,11 @@ void doMove() { }); // v2 -> e1[0] - moveDirector.execute(new ListAssignMove<>(variableDescriptor, v2, e1, 0)); + moveDirector.execute(new SelectorBasedListAssignMove<>(variableDescriptor, v2, e1, 0)); // v3 -> e1[1] - moveDirector.execute(new ListAssignMove<>(variableDescriptor, v3, e1, 1)); + moveDirector.execute(new SelectorBasedListAssignMove<>(variableDescriptor, v3, e1, 1)); // v1 -> e1[0] - moveDirector.execute(new ListAssignMove<>(variableDescriptor, v1, e1, 0)); + moveDirector.execute(new SelectorBasedListAssignMove<>(variableDescriptor, v1, e1, 0)); assertThat(e1.getValueList()).containsExactly(v1, v2, v3); } @@ -83,9 +82,9 @@ void isMoveDoableValueRangeProviderOnEntity() { valueRangeManager.reset(solution); // different entity => valid value - assertThat(new ListAssignMove<>(otherVariableDescriptor, v1, e1, 0).isMoveDoable(otherInnerScoreDirector)) + assertThat(new SelectorBasedListAssignMove<>(otherVariableDescriptor, v1, e1, 0).isMoveDoable(otherInnerScoreDirector)) .isTrue(); - assertThat(new ListAssignMove<>(otherVariableDescriptor, v1, e2, 0).isMoveDoable(otherInnerScoreDirector)) + assertThat(new SelectorBasedListAssignMove<>(otherVariableDescriptor, v1, e2, 0).isMoveDoable(otherInnerScoreDirector)) .isTrue(); } @@ -97,7 +96,7 @@ void rebase() { var destinationV1 = new TestdataListValue("1"); var destinationE1 = new TestdataListEntity("e1"); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { e1, destinationE1 }, @@ -105,11 +104,12 @@ void rebase() { assertSameProperties( destinationV1, destinationE1, 0, - new ListAssignMove<>(variableDescriptor, v1, e1, 0).rebase(destinationScoreDirector)); + new SelectorBasedListAssignMove<>(variableDescriptor, v1, e1, 0) + .rebase(destinationScoreDirector.getMoveDirector())); } static void assertSameProperties(Object movedValue, Object destinationEntity, int destinationIndex, - ListAssignMove move) { + SelectorBasedListAssignMove move) { assertThat(move.getMovedValue()).isSameAs(movedValue); assertThat(move.getDestinationEntity()).isSameAs(destinationEntity); assertThat(move.getDestinationIndex()).isEqualTo(destinationIndex); @@ -120,6 +120,6 @@ void toStringTest() { var v1 = new TestdataListValue("1"); var e1 = new TestdataListEntity("E1"); - assertThat(new ListAssignMove<>(variableDescriptor, v1, e1, 15)).hasToString("1 {null -> E1[15]}"); + assertThat(new SelectorBasedListAssignMove<>(variableDescriptor, v1, e1, 15)).hasToString("1 {null -> E1[15]}"); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMoveTest.java similarity index 71% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMoveTest.java index 388c924e17c..6f91c798e4f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMoveTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.stream.Stream; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; @@ -32,7 +31,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class ListChangeMoveTest { +class SelectorBasedListChangeMoveTest { private final TestdataListValue v0 = new TestdataListValue("0"); private final TestdataListValue v1 = new TestdataListValue("1"); @@ -56,17 +55,17 @@ void setUp() { @Test void isMoveDoable() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); // same entity, same index => not doable because the move doesn't change anything - assertThat(new ListChangeMove<>(variableDescriptor, e1, 1, e1, 1).isMoveDoable(scoreDirector)).isFalse(); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 1, e1, 1).isMoveDoable(scoreDirector)).isFalse(); // same entity, different index => doable - assertThat(new ListChangeMove<>(variableDescriptor, e1, 0, e1, 1).isMoveDoable(scoreDirector)).isTrue(); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e1, 1).isMoveDoable(scoreDirector)).isTrue(); // same entity, index == list size => not doable because the element is first removed (list size is reduced by 1) - assertThat(new ListChangeMove<>(variableDescriptor, e1, 0, e1, 2).isMoveDoable(scoreDirector)).isFalse(); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e1, 2).isMoveDoable(scoreDirector)).isFalse(); // different entity => doable - assertThat(new ListChangeMove<>(variableDescriptor, e1, 0, e2, 0).isMoveDoable(scoreDirector)).isTrue(); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e2, 0).isMoveDoable(scoreDirector)).isTrue(); } @Disabled("Temporarily disabled") @@ -78,23 +77,23 @@ void isMoveDoableValueRangeProviderOnEntity() { var entity1 = new TestdataListEntityProvidingEntity("e1", List.of(value1, value2), List.of(value1, value2)); var entity2 = new TestdataListEntityProvidingEntity("e2", List.of(value1, value3), List.of(value3)); // different entity => valid value - assertThat(new ListChangeMove<>(otherVariableDescriptor, entity1, 0, entity2, 0) + assertThat(new SelectorBasedListChangeMove<>(otherVariableDescriptor, entity1, 0, entity2, 0) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); // different entity => invalid value - assertThat(new ListChangeMove<>(otherVariableDescriptor, entity1, 1, entity2, 0) + assertThat(new SelectorBasedListChangeMove<>(otherVariableDescriptor, entity1, 1, entity2, 0) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); } @Test void doMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - ListChangeMove move = new ListChangeMove<>(variableDescriptor, e1, 1, e2, 1); + var move = new SelectorBasedListChangeMove<>(variableDescriptor, e1, 1, e2, 1); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1); assertThat(e2.getValueList()).containsExactly(v3, v2); @@ -103,7 +102,6 @@ void doMove() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 1, 1); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 1, 1); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 1, 2); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @@ -125,12 +123,11 @@ static Stream doAndUndoMoveOnTheSameEntity() { @MethodSource void doAndUndoMoveOnTheSameEntity(int destinationIndex, List expectedValueList, int fromIndex, int toIndex) { // Given... - final int sourceIndex = 2; // we're always moving V2 - TestdataListEntity e = new TestdataListEntity("E", v0, v1, v2, v3, v4); + final var sourceIndex = 2; // we're always moving V2 + var e = new TestdataListEntity("E", v0, v1, v2, v3, v4); // When V2 is moved to destinationIndex... - ListChangeMove move = - new ListChangeMove<>(variableDescriptor, e, sourceIndex, e, destinationIndex); + var move = new SelectorBasedListChangeMove<>(variableDescriptor, e, sourceIndex, e, destinationIndex); // Some destinationIndexes make the move ephemeral. if (expectedValueList == null) { @@ -141,7 +138,7 @@ void doAndUndoMoveOnTheSameEntity(int destinationIndex, List expectedVal // Otherwise, the move is doable... assertThat(move.isMoveDoable(scoreDirector)).isTrue(); // ...and when it's done... - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); // ...V2 ends up at the destinationIndex assertThat(e.getValueList().indexOf(v2)).isEqualTo(destinationIndex); assertThat((TestdataListValue) variableDescriptor.getElement(e, destinationIndex)).isEqualTo(v2); @@ -150,22 +147,21 @@ void doAndUndoMoveOnTheSameEntity(int destinationIndex, List expectedVal verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e, fromIndex, toIndex); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e, fromIndex, toIndex); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void rebase() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - TestdataListValue destinationV1 = new TestdataListValue("1"); - TestdataListValue destinationV2 = new TestdataListValue("2"); - TestdataListValue destinationV3 = new TestdataListValue("3"); - TestdataListEntity destinationE1 = new TestdataListEntity("e1", destinationV1, destinationV2); - TestdataListEntity destinationE2 = new TestdataListEntity("e2", destinationV3); + var destinationV1 = new TestdataListValue("1"); + var destinationV2 = new TestdataListValue("2"); + var destinationV3 = new TestdataListValue("3"); + var destinationE1 = new TestdataListEntity("e1", destinationV1, destinationV2); + var destinationE2 = new TestdataListEntity("e2", destinationV3); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -177,15 +173,17 @@ void rebase() { assertSameProperties( destinationE1, 0, destinationV1, destinationE2, 1, - new ListChangeMove<>(variableDescriptor, e1, 0, e2, 1).rebase(destinationScoreDirector)); + new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e2, 1) + .rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties( destinationE2, 0, destinationV3, destinationE2, 0, - new ListChangeMove<>(variableDescriptor, e2, 0, e2, 0).rebase(destinationScoreDirector)); + new SelectorBasedListChangeMove<>(variableDescriptor, e2, 0, e2, 0) + .rebase(destinationScoreDirector.getMoveDirector())); } static void assertSameProperties(Object sourceEntity, int sourceIndex, Object movedValue, Object destinationEntity, - int destinationIndex, ListChangeMove move) { + int destinationIndex, SelectorBasedListChangeMove move) { assertThat(move.getSourceEntity()).isSameAs(sourceEntity); assertThat(move.getSourceIndex()).isEqualTo(sourceIndex); assertThat(move.getMovedValue()).isSameAs(movedValue); @@ -195,34 +193,34 @@ static void assertSameProperties(Object sourceEntity, int sourceIndex, Object mo @Test void tabuIntrospection_twoEntities() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - ListChangeMove moveTwoEntities = new ListChangeMove<>(variableDescriptor, e1, 1, e2, 1); + var moveTwoEntities = new SelectorBasedListChangeMove<>(variableDescriptor, e1, 1, e2, 1); // Do the move first because that might affect the returned values. - moveTwoEntities.doMoveOnGenuineVariables(scoreDirector); + moveTwoEntities.execute(scoreDirector); assertThat(moveTwoEntities.getPlanningEntities()).containsExactly(e1, e2); assertThat(moveTwoEntities.getPlanningValues()).containsExactly(v2); } @Test void tabuIntrospection_oneEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); + var e1 = new TestdataListEntity("e1", v1, v2); - ListChangeMove moveOneEntity = new ListChangeMove<>(variableDescriptor, e1, 0, e1, 1); + var moveOneEntity = new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e1, 1); // Do the move first because that might affect the returned values. - moveOneEntity.doMoveOnGenuineVariables(scoreDirector); + moveOneEntity.execute(scoreDirector); assertThat(moveOneEntity.getPlanningEntities()).containsExactly(e1); assertThat(moveOneEntity.getPlanningValues()).containsExactly(v1); } @Test void toStringTest() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - assertThat(new ListChangeMove<>(variableDescriptor, e1, 1, e1, 0)).hasToString("2 {e1[1] -> e1[0]}"); - assertThat(new ListChangeMove<>(variableDescriptor, e1, 0, e2, 1)).hasToString("1 {e1[0] -> e2[1]}"); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 1, e1, 0)).hasToString("2 {e1[1] -> e1[0]}"); + assertThat(new SelectorBasedListChangeMove<>(variableDescriptor, e1, 0, e2, 1)).hasToString("1 {e1[0] -> e2[1]}"); } @Test diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListRuinRecreateMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListRuinRecreateMoveTest.java similarity index 70% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListRuinRecreateMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListRuinRecreateMoveTest.java index c567bf83d76..59640f4771b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListRuinRecreateMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListRuinRecreateMoveTest.java @@ -6,13 +6,13 @@ import static org.mockito.Mockito.mock; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin.ListRuinRecreateMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin.SelectorBasedListRuinRecreateMove; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; @@ -20,9 +20,9 @@ import org.junit.jupiter.api.Test; -class ListRuinRecreateMoveTest { +class SelectorBasedListRuinRecreateMoveTest { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) @Test void rebase() { var variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); @@ -48,15 +48,15 @@ void rebase() { { e3, destinationE3 }, }); - var move = new ListRuinRecreateMove(mock(ListVariableDescriptor.class), + var move = new SelectorBasedListRuinRecreateMove(mock(ListVariableDescriptor.class), mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), Arrays.asList(v1, v2), - Set.of(e1, e2, e3)); - var rebasedMove = move.rebase(destinationScoreDirector); + new LinkedHashSet<>(Set.of(e1, e2, e3))); + var rebasedMove = move.rebase(destinationScoreDirector.getMoveDirector()); assertSoftly(softly -> { - softly.assertThat((Collection) rebasedMove.getPlanningEntities()) + softly.assertThat(rebasedMove.getPlanningEntities()) .containsExactlyInAnyOrder(destinationE1, destinationE2, destinationE3); // The input set is not ordered. - softly.assertThat((Collection) rebasedMove.getPlanningValues()) + softly.assertThat(rebasedMove.getPlanningValues()) .containsExactly(destinationV1, destinationV2); }); @@ -71,27 +71,28 @@ void equality() { var e2 = new TestdataListEntity("e2"); var descriptor = mock(ListVariableDescriptor.class); - var move = new ListRuinRecreateMove(descriptor, + var move = new SelectorBasedListRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); - var sameMove = new ListRuinRecreateMove(descriptor, + new LinkedHashSet<>(Set.of(v1))); + var sameMove = new SelectorBasedListRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isEqualTo(sameMove); - var differentMove = new ListRuinRecreateMove(descriptor, + var differentMove = new SelectorBasedListRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v2)); + new LinkedHashSet<>(Set.of(v2))); assertThat(move).isNotEqualTo(differentMove); - var anotherDifferentMove = new ListRuinRecreateMove(descriptor, + var anotherDifferentMove = new SelectorBasedListRuinRecreateMove(descriptor, mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e2), - Set.of(v1)); + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isNotEqualTo(anotherDifferentMove); - var yetAnotherDifferentMove = new ListRuinRecreateMove(mock(ListVariableDescriptor.class), - mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), - Set.of(v1)); + var yetAnotherDifferentMove = + new SelectorBasedListRuinRecreateMove(mock(ListVariableDescriptor.class), + mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1), + new LinkedHashSet<>(Set.of(v1))); assertThat(move).isNotEqualTo(yetAnotherDifferentMove); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMoveTest.java similarity index 70% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMoveTest.java index 18b5873121d..0c04dfd7a6e 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMoveTest.java @@ -9,7 +9,6 @@ import java.util.List; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.move.MoveDirector; @@ -25,7 +24,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ListSwapMoveTest { +class SelectorBasedListSwapMoveTest { private final TestdataListValue v1 = new TestdataListValue("1"); private final TestdataListValue v2 = new TestdataListValue("2"); @@ -48,15 +47,16 @@ void setUp() { @Test void isMoveDoable() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); // same entity, same index => not doable because the move doesn't change anything - assertThat(new ListSwapMove<>(variableDescriptor, e1, 1, e1, 1).isMoveDoable(innerScoreDirector)).isFalse(); + assertThat(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 1, e1, 1).isMoveDoable(innerScoreDirector)) + .isFalse(); // same entity, different index => doable - assertThat(new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1).isMoveDoable(innerScoreDirector)).isTrue(); + assertThat(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e1, 1).isMoveDoable(innerScoreDirector)).isTrue(); // different entity => doable - assertThat(new ListSwapMove<>(variableDescriptor, e1, 0, e2, 0).isMoveDoable(innerScoreDirector)).isTrue(); + assertThat(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e2, 0).isMoveDoable(innerScoreDirector)).isTrue(); } @Test @@ -72,19 +72,19 @@ void isMoveDoableValueRangeProviderOnEntity() { valueRangeManager.reset(solution); // different entity => valid left and right - assertThat(new ListSwapMove<>(otherVariableDescriptor, entity1, 0, entity2, 0) + assertThat(new SelectorBasedListSwapMove<>(otherVariableDescriptor, entity1, 0, entity2, 0) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); } @Test void doMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); var moveDirector = new MoveDirector<>(innerScoreDirector); // Swap Move 1: between two entities - moveDirector.executeTemporary(new ListSwapMove<>(variableDescriptor, e1, 0, e2, 0), + moveDirector.executeTemporary(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e2, 0), (__, ___) -> { assertThat(e1.getValueList()).containsExactly(v3, v2); assertThat(e2.getValueList()).containsExactly(v1); @@ -98,22 +98,22 @@ void doMove() { }); // Swap Move 2: same entity - moveDirector.execute(new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1)); + moveDirector.execute(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e1, 1)); assertThat(e1.getValueList()).containsExactly(v2, v1); } @Test void rebase() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - TestdataListValue destinationV1 = new TestdataListValue("1"); - TestdataListValue destinationV2 = new TestdataListValue("2"); - TestdataListValue destinationV3 = new TestdataListValue("3"); - TestdataListEntity destinationE1 = new TestdataListEntity("e1", destinationV1, destinationV2); - TestdataListEntity destinationE2 = new TestdataListEntity("e2", destinationV3); + var destinationV1 = new TestdataListValue("1"); + var destinationV2 = new TestdataListValue("2"); + var destinationV3 = new TestdataListValue("3"); + var destinationE1 = new TestdataListEntity("e1", destinationV1, destinationV2); + var destinationE2 = new TestdataListEntity("e2", destinationV3); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + var destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -125,17 +125,19 @@ void rebase() { assertSameProperties( destinationE1, 1, destinationV2, destinationE2, 0, destinationV3, - new ListSwapMove<>(variableDescriptor, e1, 1, e2, 0).rebase(destinationScoreDirector)); + new SelectorBasedListSwapMove<>(variableDescriptor, e1, 1, e2, 0) + .rebase(destinationScoreDirector.getMoveDirector())); assertSameProperties( destinationE1, 0, destinationV1, destinationE1, 1, destinationV2, - new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1).rebase(destinationScoreDirector)); + new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e1, 1) + .rebase(destinationScoreDirector.getMoveDirector())); } static void assertSameProperties( Object leftEntity, int leftIndex, Object leftValue, Object rightEntity, int rightIndex, Object rightValue, - ListSwapMove move) { + SelectorBasedListSwapMove move) { assertThat(move.getLeftEntity()).isSameAs(leftEntity); assertThat(move.getLeftIndex()).isEqualTo(leftIndex); assertThat(move.getLeftValue()).isSameAs(leftValue); @@ -146,34 +148,34 @@ static void assertSameProperties( @Test void tabuIntrospection_twoEntities() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - ListSwapMove moveTwoEntities = new ListSwapMove<>(variableDescriptor, e1, 0, e2, 0); + var moveTwoEntities = new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e2, 0); // Do the move first because that might affect the returned values. - moveTwoEntities.doMoveOnGenuineVariables(innerScoreDirector); + moveTwoEntities.execute(innerScoreDirector); assertThat(moveTwoEntities.getPlanningEntities()).containsExactly(e1, e2); assertThat(moveTwoEntities.getPlanningValues()).containsExactlyInAnyOrder(v3, v1); } @Test void tabuIntrospection_oneEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); + var e1 = new TestdataListEntity("e1", v1, v2); - ListSwapMove moveOneEntity = new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1); + var moveOneEntity = new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e1, 1); // Do the move first because that might affect the returned values. - moveOneEntity.doMoveOnGenuineVariables(innerScoreDirector); + moveOneEntity.execute(innerScoreDirector); assertThat(moveOneEntity.getPlanningEntities()).containsExactly(e1); assertThat(moveOneEntity.getPlanningValues()).containsExactly(v2, v1); } @Test void toStringTest() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("e2", v3); + var e1 = new TestdataListEntity("e1", v1, v2); + var e2 = new TestdataListEntity("e2", v3); - assertThat(new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1)).hasToString("1 {e1[0]} <-> 2 {e1[1]}"); - assertThat(new ListSwapMove<>(variableDescriptor, e1, 1, e2, 0)).hasToString("2 {e1[1]} <-> 3 {e2[0]}"); + assertThat(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 0, e1, 1)).hasToString("1 {e1[0]} <-> 2 {e1[1]}"); + assertThat(new SelectorBasedListSwapMove<>(variableDescriptor, e1, 1, e2, 0)).hasToString("2 {e1[1]} <-> 3 {e2[0]}"); } @Test diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMoveTest.java similarity index 74% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMoveTest.java index f2a4389df2d..6c6c2cea379 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListUnassignMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMoveTest.java @@ -8,6 +8,7 @@ import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; @@ -15,7 +16,7 @@ import org.junit.jupiter.api.Test; -class ListUnassignMoveTest { +class SelectorBasedListUnassignMoveTest { @Test void doMove() { @@ -28,20 +29,19 @@ void doMove() { var variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); // Unassign last - var move = new ListUnassignMove<>(variableDescriptor, e1, 2); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedListUnassignMove<>(variableDescriptor, e1, 2); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 2, 3); verify(scoreDirector).beforeListVariableElementUnassigned(variableDescriptor, v3); verify(scoreDirector).afterListVariableElementUnassigned(variableDescriptor, v3); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 2, 2); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); // Unassign the rest - new ListUnassignMove<>(variableDescriptor, e1, 0).doMoveOnly(scoreDirector); - new ListUnassignMove<>(variableDescriptor, e1, 0).doMoveOnly(scoreDirector); + new SelectorBasedListUnassignMove<>(variableDescriptor, e1, 0).execute(scoreDirector); + new SelectorBasedListUnassignMove<>(variableDescriptor, e1, 0).execute(scoreDirector); assertThat(e1.getValueList()).isEmpty(); } @@ -56,8 +56,8 @@ void undoMove() { var variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); - var move = new ListUnassignMove<>(variableDescriptor, e1, 2); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedListUnassignMove<>(variableDescriptor, e1, 2); + move.execute(scoreDirector); assertThat(e1.getValueList()).hasSize(2); verify(scoreDirector).beforeListVariableElementUnassigned(variableDescriptor, v3); verify(scoreDirector).afterListVariableElementUnassigned(variableDescriptor, v3); @@ -66,9 +66,13 @@ void undoMove() { @Test void rebase() { var solutionDescriptor = TestdataSolution.buildSolutionDescriptor(); - var destinationScoreDirector = mockRebasingScoreDirector(solutionDescriptor, new Object[][] {}); - var move = new ListUnassignMove(null, null, 0); - var rebasedMove = move.rebase(destinationScoreDirector); + var source = new TestdataEntity(); + var destination = new TestdataEntity(); + var destinationScoreDirector = mockRebasingScoreDirector(solutionDescriptor, new Object[][] { + { source, destination }, + }); + var move = new SelectorBasedListUnassignMove(null, source, 0); + var rebasedMove = move.rebase(destinationScoreDirector.getMoveDirector()); assertThat(rebasedMove).isNotSameAs(move); } @@ -80,6 +84,6 @@ void toStringTest() { var variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); - assertThat(new ListUnassignMove<>(variableDescriptor, e1, 0)).hasToString("1 {E1[0] -> null}"); + assertThat(new SelectorBasedListUnassignMove<>(variableDescriptor, e1, 0)).hasToString("1 {E1[0] -> null}"); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMoveTest.java similarity index 58% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMoveTest.java index adbf3e5567b..99044cd39b7 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMoveTest.java @@ -9,7 +9,6 @@ import java.util.List; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; @@ -23,7 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class SubListChangeMoveTest { +class SelectorBasedSubListChangeMoveTest { private final TestdataListValue v1 = new TestdataListValue("1"); private final TestdataListValue v2 = new TestdataListValue("2"); @@ -49,19 +48,24 @@ void setUp() { @Test void isMoveDoable() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); // same entity, same index => not doable because the move doesn't change anything - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 2, e1, 1, false).isMoveDoable(scoreDirector)).isFalse(); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 2, e1, 1, false).isMoveDoable(scoreDirector)) + .isFalse(); // same entity, different index => doable - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 2, e1, 0, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 2, e1, 0, false).isMoveDoable(scoreDirector)) + .isTrue(); // same entity, index + length <= list size => doable - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 0, 3, e1, 1, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 3, e1, 1, false).isMoveDoable(scoreDirector)) + .isTrue(); // same entity, index + length > list size => not doable - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 0, 3, e1, 2, false).isMoveDoable(scoreDirector)).isFalse(); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 3, e1, 2, false).isMoveDoable(scoreDirector)) + .isFalse(); // different entity => doable - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false).isMoveDoable(scoreDirector)) + .isTrue(); } @Test @@ -79,40 +83,40 @@ void isMoveDoableValueRangeProviderOnEntity() { // different entity => valid sublist assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, false) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, false) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, true) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, true) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); // different entity => invalid sublist assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, false) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, false) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, true) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, true) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 1, 2, entity2, 0, false) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 1, 2, entity2, 0, false) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); assertThat( - new SubListChangeMove<>(otherVariableDescriptor, entity1, 1, 2, entity2, 0, true) + new SelectorBasedSubListChangeMove<>(otherVariableDescriptor, entity1, 1, 2, entity2, 0, true) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); } @Test void doMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); - SubListChangeMove move = new SubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false); + var move = new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v4); assertThat(e2.getValueList()).containsExactly(v2, v3, v5); @@ -121,18 +125,17 @@ void doMove() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 1, 1); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 0, 0); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 0, 2); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void doReversingMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); - SubListChangeMove move = new SubListChangeMove<>(variableDescriptor, e1, 0, 3, e2, 1, true); + var move = new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 3, e2, 1, true); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v4); assertThat(e2.getValueList()).containsExactly(v5, v3, v2, v1); @@ -141,60 +144,53 @@ void doReversingMove() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 0); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 1, 1); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 1, 4); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void doMoveOnSameEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4, v5, v6); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4, v5, v6); - SubListChangeMove move = - new SubListChangeMove<>(variableDescriptor, e1, 3, 2, e1, 0, false); + var move = new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 3, 2, e1, 0, false); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v4, v5, v1, v2, v3, v6); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 5); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 5); - // TODO or this more fine-grained? (Do we allow multiple notifications per entity? (Yes)) - // verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 3, 5); - // verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 5, 5); - // verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 0); - // verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 2); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void rebase() { - TestdataListEntity e1 = new TestdataListEntity("e1"); - TestdataListEntity e2 = new TestdataListEntity("e2"); + var e1 = new TestdataListEntity("e1"); + var e2 = new TestdataListEntity("e2"); - TestdataListEntity destinationE1 = new TestdataListEntity("e1"); - TestdataListEntity destinationE2 = new TestdataListEntity("e2"); + var destinationE1 = new TestdataListEntity("e1"); + var destinationE2 = new TestdataListEntity("e2"); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { e1, destinationE1 }, { e2, destinationE2 }, }); - int sourceIndex = 3; - int length = 5; - int destinationIndex = 7; - boolean reversing = false; + var sourceIndex = 3; + var length = 5; + var destinationIndex = 7; + var reversing = false; assertSameProperties(destinationE1, sourceIndex, length, destinationE2, destinationIndex, reversing, - new SubListChangeMove<>(variableDescriptor, e1, sourceIndex, length, e2, destinationIndex, reversing) - .rebase(destinationScoreDirector)); + new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, sourceIndex, length, e2, destinationIndex, + reversing) + .rebase(destinationScoreDirector.getMoveDirector())); } static void assertSameProperties( Object sourceEntity, int fromIndex, int length, Object destinationEntity, int destinationIndex, boolean reversing, - SubListChangeMove move) { + SelectorBasedSubListChangeMove move) { assertThat(move.getSourceEntity()).isSameAs(sourceEntity); assertThat(move.getFromIndex()).isEqualTo(fromIndex); assertThat(move.getSubListSize()).isSameAs(length); @@ -205,52 +201,58 @@ static void assertSameProperties( @Test void tabuIntrospection_twoEntities() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); - TestdataListEntity e3 = new TestdataListEntity("e3"); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); + var e3 = new TestdataListEntity("e3"); - SubListChangeMove moveTwoEntities = - new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, false); + var moveTwoEntities = new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, false); // Do the move first because that might affect the returned values. - moveTwoEntities.doMoveOnGenuineVariables(scoreDirector); + moveTwoEntities.execute(scoreDirector); assertThat(moveTwoEntities.getPlanningEntities()).containsExactly(e1, e2); assertThat(moveTwoEntities.getPlanningValues()).containsExactly(v2, v3, v4); - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, true)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, true)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e3, 0, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e3, 0, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 2, e2, 0, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 2, 4, e2, 0, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 2, 4, e2, 0, false)) + .isNotEqualTo(moveTwoEntities); // ^ ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e3, 1, 3, e2, 0, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e3, 1, 3, e2, 0, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, false)).isEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e2, 0, false)).isEqualTo(moveTwoEntities); } @Test void tabuIntrospection_oneEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - SubListChangeMove moveOneEntity = - new SubListChangeMove<>(variableDescriptor, e1, 0, 2, e1, 2, false); + var moveOneEntity = new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 2, e1, 2, false); // Do the move first because that might affect the returned values. - moveOneEntity.doMoveOnGenuineVariables(scoreDirector); + moveOneEntity.execute(scoreDirector); assertThat(moveOneEntity.getPlanningEntities()).containsExactly(e1); assertThat(moveOneEntity.getPlanningValues()).containsExactly(v1, v2); } @Test void toStringTest() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); - - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 1, 3, e1, 0, false)).hasToString("|3| {e1[1..4] -> e1[0]}"); - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 0, 1, e2, 1, false)).hasToString("|1| {e1[0..1] -> e2[1]}"); - assertThat(new SubListChangeMove<>(variableDescriptor, e1, 0, 1, e2, 1, true)) + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); + + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 1, 3, e1, 0, false)) + .hasToString("|3| {e1[1..4] -> e1[0]}"); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 1, e2, 1, false)) + .hasToString("|1| {e1[0..1] -> e2[1]}"); + assertThat(new SelectorBasedSubListChangeMove<>(variableDescriptor, e1, 0, 1, e2, 1, true)) .hasToString("|1| {e1[0..1] -reversing-> e2[1]}"); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMoveTest.java similarity index 58% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMoveTest.java index 441dcf0d158..50676ed1161 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMoveTest.java @@ -9,9 +9,7 @@ import java.util.List; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.ValueRangeManager; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; @@ -24,7 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class SubListSwapMoveTest { +class SelectorBasedSubListSwapMoveTest { private final TestdataListValue v1 = new TestdataListValue("1"); private final TestdataListValue v2 = new TestdataListValue("2"); @@ -52,21 +50,33 @@ void setUp() { @Test void isMoveDoable() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); // same entity, overlap => not doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e1, 2, 5, false).isMoveDoable(scoreDirector)).isFalse(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e1, 2, 5, false).isMoveDoable(scoreDirector)) + .isFalse(); // same entity, overlap => not doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 5, e1, 1, 2, false).isMoveDoable(scoreDirector)).isFalse(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 5, e1, 1, 2, false).isMoveDoable(scoreDirector)) + .isFalse(); // same entity, no overlap (with gap) => doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 1, e1, 4, 5, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 1, e1, 4, 5, false).isMoveDoable(scoreDirector)) + .isTrue(); // same entity, no overlap (with touch) => doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e1, 3, 5, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e1, 3, 5, false).isMoveDoable(scoreDirector)) + .isTrue(); // same entity, no overlap (with touch, right below left) => doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 2, 5, e1, 0, 2, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 2, 5, e1, 0, 2, false).isMoveDoable(scoreDirector)) + .isTrue(); // different entities => doable - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 5, e2, 0, 1, false).isMoveDoable(scoreDirector)).isTrue(); + assertThat( + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 5, e2, 0, 1, false).isMoveDoable(scoreDirector)) + .isTrue(); } @Test @@ -86,41 +96,41 @@ void isMoveDoableValueRangeProviderOnEntity() { // different entity => valid sublist assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 1, false) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 1, false) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 1, true) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 1, true) .isMoveDoable(otherInnerScoreDirector)) .isTrue(); // different entity => invalid left sublist assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, 1, false) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, 1, false) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, 1, true) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 3, entity2, 0, 1, true) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); // different entity => invalid right sublist assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 2, false) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 2, false) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); assertThat( - new SubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 2, true) + new SelectorBasedSubListSwapMove<>(otherVariableDescriptor, entity1, 0, 2, entity2, 0, 2, true) .isMoveDoable(otherInnerScoreDirector)) .isFalse(); } @Test void doMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); - SubListSwapMove move = new SubListSwapMove<>(variableDescriptor, e1, 1, 3, e2, 0, 1, false); + var move = new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 1, 3, e2, 0, 1, false); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v5, v4); assertThat(e2.getValueList()).containsExactly(v2, v3); @@ -129,18 +139,17 @@ void doMove() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 1, 2); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 0, 1); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 0, 2); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void doReversingMove() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5, v6); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5, v6); - SubListSwapMove move = new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 2, true); + var move = new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 2, true); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v6, v5, v4); assertThat(e2.getValueList()).containsExactly(v3, v2, v1); @@ -149,66 +158,60 @@ void doReversingMove() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 2); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 0, 2); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 0, 3); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void doMoveOnSameEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4, v5, v6, v7); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4, v5, v6, v7); - SubListSwapMove move = new SubListSwapMove<>(variableDescriptor, e1, 0, 1, e1, 4, 7, false); + var move = new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 1, e1, 4, 7, false); - move.doMoveOnly(scoreDirector); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v5, v6, v7, v2, v3, v4, v1); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 7); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 7); - // TODO or this more fine-grained? (Do we allow multiple notifications per entity? (Yes)) - // verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 1); - // verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 3); - // verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 4, 7); - // verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 6, 7); - verify(scoreDirector).triggerVariableListeners(); verifyNoMoreInteractions(scoreDirector); } @Test void rebase() { - TestdataListEntity e1 = new TestdataListEntity("e1"); - TestdataListEntity e2 = new TestdataListEntity("e2"); + var e1 = new TestdataListEntity("e1"); + var e2 = new TestdataListEntity("e2"); - TestdataListEntity destinationE1 = new TestdataListEntity("e1"); - TestdataListEntity destinationE2 = new TestdataListEntity("e2"); + var destinationE1 = new TestdataListEntity("e1"); + var destinationE2 = new TestdataListEntity("e2"); - ScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { e1, destinationE1 }, { e2, destinationE2 }, }); - boolean reversing = false; - int leftFromIndex = 7; - int leftToIndex = 11; - int rightFromIndex = 3; - int rightToIndex = 9; + var reversing = false; + var leftFromIndex = 7; + var leftToIndex = 11; + var rightFromIndex = 3; + var rightToIndex = 9; assertSameProperties(destinationE1, leftFromIndex, leftToIndex, destinationE2, rightFromIndex, rightToIndex, reversing, - new SubListSwapMove<>(variableDescriptor, e1, leftFromIndex, leftToIndex, e2, rightFromIndex, rightToIndex, + new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, leftFromIndex, leftToIndex, e2, rightFromIndex, + rightToIndex, reversing) - .rebase(destinationScoreDirector)); + .rebase(destinationScoreDirector.getMoveDirector())); } static void assertSameProperties( Object leftEntity, int leftFromIndex, int leftToIndex, Object rightEntity, int rightFromIndex, int rightToIndex, boolean reversing, - SubListSwapMove move) { - SubList leftSubList = move.getLeftSubList(); + SelectorBasedSubListSwapMove move) { + var leftSubList = move.getLeftSubList(); assertThat(leftSubList.entity()).isSameAs(leftEntity); assertThat(leftSubList.fromIndex()).isEqualTo(leftFromIndex); assertThat(leftSubList.getToIndex()).isEqualTo(leftToIndex); - SubList rightSubList = move.getRightSubList(); + var rightSubList = move.getRightSubList(); assertThat(rightSubList.entity()).isSameAs(rightEntity); assertThat(rightSubList.fromIndex()).isEqualTo(rightFromIndex); assertThat(rightSubList.getToIndex()).isEqualTo(rightToIndex); @@ -217,59 +220,65 @@ static void assertSameProperties( @Test void tabuIntrospection_twoEntities() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5); - TestdataListEntity e3 = new TestdataListEntity("e3"); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5); + var e3 = new TestdataListEntity("e3"); - SubListSwapMove moveTwoEntities = - new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, false); + var moveTwoEntities = new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, false); // Do the move first because that might affect the returned values. - moveTwoEntities.doMoveOnGenuineVariables(scoreDirector); + moveTwoEntities.execute(scoreDirector); assertThat(moveTwoEntities.getPlanningEntities()).containsExactly(e1, e2); assertThat(moveTwoEntities.getPlanningValues()).containsExactly(v1, v2, v3, v5); - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, true)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, true)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 2, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 2, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 1, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 1, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e3, 0, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e3, 0, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 1, 4, e2, 0, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 1, 4, e2, 0, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 1, 3, e2, 0, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 1, 3, e2, 0, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e3, 0, 3, e2, 0, 1, false)).isNotEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e3, 0, 3, e2, 0, 1, false)) + .isNotEqualTo(moveTwoEntities); // ^ - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, false)).isEqualTo(moveTwoEntities); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 3, e2, 0, 1, false)) + .isEqualTo(moveTwoEntities); } @Test void tabuIntrospection_oneEntity() { - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - SubListSwapMove moveOneEntity = - new SubListSwapMove<>(variableDescriptor, e1, 3, 4, e1, 0, 2, false); + var moveOneEntity = new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 3, 4, e1, 0, 2, false); // Do the move first because that might affect the returned values. - moveOneEntity.doMoveOnGenuineVariables(scoreDirector); + moveOneEntity.execute(scoreDirector); assertThat(moveOneEntity.getPlanningEntities()).containsExactly(e1); assertThat(moveOneEntity.getPlanningValues()).containsExactly(v1, v2, v4); // Swaps on the same entity are normalized so that the lower index subList is always treated as the left one. - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 2, e1, 3, 4, false)).isEqualTo(moveOneEntity); + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 2, e1, 3, 4, false)).isEqualTo(moveOneEntity); } @Test void toStringTest() { - TestdataListEntity e1 = new TestdataListEntity("e1"); - TestdataListEntity e2 = new TestdataListEntity("e2"); + var e1 = new TestdataListEntity("e1"); + var e2 = new TestdataListEntity("e2"); - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 1, 4, e1, 0, 1, false)) + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 1, 4, e1, 0, 1, false)) .hasToString("{e1[0..1]} <-> {e1[1..4]}"); - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 1, e2, 1, 6, false)) + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 1, e2, 1, 6, false)) .hasToString("{e1[0..1]} <-> {e2[1..6]}"); - assertThat(new SubListSwapMove<>(variableDescriptor, e1, 0, 1, e2, 1, 6, true)) + assertThat(new SelectorBasedSubListSwapMove<>(variableDescriptor, e1, 0, 1, e2, 1, 6, true)) .hasToString("{e1[0..1]} <-reversing-> {e2[1..6]}"); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIteratorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIteratorTest.java index bcbcbbcdc77..9ff1d0ab49c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIteratorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIteratorTest.java @@ -12,7 +12,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.function.Function; import java.util.stream.Collectors; @@ -46,13 +45,13 @@ private static class KOptListMoveIteratorMockData { @SuppressWarnings("unchecked") private KOptListMoveIteratorMockData createMockKOptListMoveIterator(int minK, int maxK, int[] pickedKDistribution) { - KOptListMoveIteratorMockData result = new KOptListMoveIteratorMockData(); + var result = new KOptListMoveIteratorMockData(); result.minK = minK; result.maxK = maxK; result.pickedKDistribution = pickedKDistribution; result.distributionSum = 0; - for (int i = 0; i < pickedKDistribution.length; i++) { - result.distributionSum += pickedKDistribution[i]; + for (var j : pickedKDistribution) { + result.distributionSum += j; } result.workingRandom = mock(Random.class); result.listVariableDescriptor = mock(ListVariableDescriptor.class); @@ -82,10 +81,10 @@ private static class KOptMoveInfo { int[] addedEdgeIndexToOtherEndpoint; List entityList; - public void verify(KOptListMove kOptListMove) { - KOptDescriptor descriptor = kOptListMove.getDescriptor(); + public void verify(SelectorBasedKOptListMove kOptListMove) { + var descriptor = kOptListMove.getDescriptor(); assertThat(descriptor.k()).isEqualTo(removedEdgeList.size() / 2); - List expectedRemoveEdges = new ArrayList<>(descriptor.k() * 2 + 1); + var expectedRemoveEdges = new ArrayList<>(descriptor.k() * 2 + 1); expectedRemoveEdges.add(null); expectedRemoveEdges.addAll(removedEdgeList); assertThat((Object[]) descriptor.removedEdges()).containsExactly(expectedRemoveEdges.toArray()); @@ -104,33 +103,33 @@ private KOptMoveInfo setupValidOddSequentialKOptMove(KOptListMoveIteratorMockDat if (k % 2 != 1) { throw new IllegalArgumentException("Function can only be used for odd k (" + k + " is not odd)."); } - int randomValue = 0; - for (int i = mocks.minK; i < k; i++) { + var randomValue = 0; + for (var i = mocks.minK; i < k; i++) { randomValue += mocks.pickedKDistribution[i - mocks.minK]; } when(mocks.workingRandom.nextInt(mocks.distributionSum)).thenReturn(randomValue); - Object[] data = new Object[2 * k]; - for (int i = 0; i < data.length; i++) { + var data = new Object[2 * k]; + for (var i = 0; i < data.length; i++) { data[i] = "v" + i; } - Map entityToListSize = Arrays.stream(entities) + var entityToListSize = Arrays.stream(entities) .collect(Collectors.toMap(Function.identity(), entity -> 2, Integer::sum)); - Map> entityToList = new HashMap<>(); - Map entityToOffset = new HashMap<>(); + var entityToList = new HashMap>(); + var entityToOffset = new HashMap(); - int offset = 0; - for (Map.Entry entityAndListSize : entityToListSize.entrySet()) { - Object entity = entityAndListSize.getKey(); + var offset = 0; + for (var entityAndListSize : entityToListSize.entrySet()) { + var entity = entityAndListSize.getKey(); int listSize = entityAndListSize.getValue(); List entityList = new ArrayList<>(Arrays.asList(data).subList(offset, offset + listSize)); - for (int i = 0; i < listSize; i++) { + for (var i = 0; i < listSize; i++) { entityList.add(2 * i + 1, entity + "-extra-" + i); } - entityList.add(0, entity + "-start"); + entityList.addFirst(entity + "-start"); entityList.add(entity + "-end"); // No pinning. when(mocks.listVariableDescriptor.getValue(entity)).thenReturn(entityList); @@ -143,7 +142,7 @@ private KOptMoveInfo setupValidOddSequentialKOptMove(KOptListMoveIteratorMockDat entityToList.put(entity, entityList); entityToOffset.put(entity, 1); - for (int i = 0; i < entityList.size(); i++) { + for (var i = 0; i < entityList.size(); i++) { when(mocks.listVariableStateSupply.getElementPosition(entityList.get(i))) .thenReturn(ElementPosition.of(entity, i)); when(mocks.listVariableStateSupply.getInverseSingleton(entityList.get(i))).thenReturn(entity); @@ -156,41 +155,41 @@ private KOptMoveInfo setupValidOddSequentialKOptMove(KOptListMoveIteratorMockDat when(mocks.workingRandom.nextBoolean()).thenReturn(true); - Object firstPicked = entityToList.get(entities[0]).get(1); - Object firstSuccessor = entityToList.get(entities[0]).get(2); + var firstPicked = entityToList.get(entities[0]).get(1); + var firstSuccessor = entityToList.get(entities[0]).get(2); entityToOffset.merge(entities[0], 3, Integer::sum); when(mocks.originSelector.iterator()).thenReturn(iteratorForValues(firstPicked)); - Object[] remainingPicked = new Object[k - 1]; - Object[] remainingPickedSuccessor = new Object[k - 1]; + var remainingPicked = new Object[k - 1]; + var remainingPickedSuccessor = new Object[k - 1]; - List pickedValueList = new ArrayList<>(); - for (int i = 1; i < k; i++) { + var pickedValueList = new ArrayList<>(); + for (var i = 1; i < k; i++) { int index = entityToOffset.get(entities[i]); - Object value = entityToList.get(entities[i]).get(index); - Object valueSuccessor = entityToList.get(entities[i]).get(index + 1); + var value = entityToList.get(entities[i]).get(index); + var valueSuccessor = entityToList.get(entities[i]).get(index + 1); entityToOffset.merge(entities[i], 3, Integer::sum); - pickedValueList.add(0, value); + pickedValueList.addFirst(value); remainingPicked[remainingPicked.length - i] = value; remainingPickedSuccessor[remainingPicked.length - i] = valueSuccessor; } when(mocks.valueSelector.iterator()).thenReturn(pickedValueList.iterator()); - KOptMoveInfo out = new KOptMoveInfo(); + var out = new KOptMoveInfo(); out.removedEdgeList = new ArrayList<>(2 * k); out.entityList = Arrays.stream(entities).distinct().collect(Collectors.toList()); out.removedEdgeList.add(firstPicked); out.removedEdgeList.add(firstSuccessor); - for (int i = 0; i < remainingPicked.length; i++) { + for (var i = 0; i < remainingPicked.length; i++) { out.removedEdgeList.add(remainingPicked[i]); out.removedEdgeList.add(remainingPickedSuccessor[i]); } - Object[] tourData = new Object[out.removedEdgeList.size() + 1]; - for (int i = 0; i < out.removedEdgeList.size(); i++) { + var tourData = new Object[out.removedEdgeList.size() + 1]; + for (var i = 0; i < out.removedEdgeList.size(); i++) { tourData[i + 1] = out.removedEdgeList.get(i); } out.addedEdgeIndexToOtherEndpoint = KOptDescriptor.computeInEdgesForSequentialMove(tourData); @@ -201,37 +200,37 @@ private KOptMoveInfo setupValidOddSequentialKOptMove(KOptListMoveIteratorMockDat * @return The expected KOptMoveInfo */ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockData mocks, Object... entities) { - int k = 4; + var k = 4; if (entities.length != (k + 1)) { throw new IllegalArgumentException("Expected (" + (k + 1) + ") arguments"); } - int randomValue = 0; - for (int i = mocks.minK; i < k; i++) { + var randomValue = 0; + for (var i = mocks.minK; i < k; i++) { randomValue += mocks.pickedKDistribution[i - mocks.minK]; } when(mocks.workingRandom.nextInt(mocks.distributionSum)).thenReturn(randomValue); - Object[] data = new Object[2 * k + 8]; - for (int i = 0; i < data.length; i++) { + var data = new Object[2 * k + 8]; + for (var i = 0; i < data.length; i++) { data[i] = "v" + i; } - Map entityToListSize = Arrays.stream(entities) + var entityToListSize = Arrays.stream(entities) .collect(Collectors.toMap(Function.identity(), entity -> 2, Integer::sum)); - Map> entityToList = new HashMap<>(); - Map entityToOffset = new HashMap<>(); + var entityToList = new HashMap>(); + var entityToOffset = new HashMap(); - int offset = 0; - for (Map.Entry entityAndListSize : entityToListSize.entrySet()) { - Object entity = entityAndListSize.getKey(); + var offset = 0; + for (var entityAndListSize : entityToListSize.entrySet()) { + var entity = entityAndListSize.getKey(); int listSize = entityAndListSize.getValue(); - List entityList = new ArrayList<>(Arrays.asList(data).subList(offset, offset + listSize)); - for (int i = 0; i < listSize; i++) { + var entityList = new ArrayList<>(Arrays.asList(data).subList(offset, offset + listSize)); + for (var i = 0; i < listSize; i++) { entityList.add(2 * i + 1, entity + "-extra-" + i); } - entityList.add(0, entity + "-start"); + entityList.addFirst(entity + "-start"); entityList.add(entity + "-end"); // No pinning. when(mocks.listVariableDescriptor.getValue(entity)).thenReturn(entityList); @@ -248,7 +247,7 @@ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockDat entityToList.put(entity, entityList); entityToOffset.put(entity, 1); - for (int i = 0; i < entityList.size(); i++) { + for (var i = 0; i < entityList.size(); i++) { when(mocks.listVariableStateSupply.getElementPosition(entityList.get(i))) .thenReturn(ElementPosition.of(entity, i)); when(mocks.listVariableStateSupply.getInverseSingleton(entityList.get(i))).thenReturn(entity); @@ -261,21 +260,21 @@ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockDat when(mocks.workingRandom.nextBoolean()).thenReturn(true); - Object firstPicked = entityToList.get(entities[0]).get(1); - Object firstSuccessor = entityToList.get(entities[0]).get(2); + var firstPicked = entityToList.get(entities[0]).get(1); + var firstSuccessor = entityToList.get(entities[0]).get(2); entityToOffset.merge(entities[0], 3, Integer::sum); when(mocks.originSelector.iterator()).thenReturn(iteratorForValues(firstPicked)); - Object[] remainingPicked = new Object[k]; - Object[] remainingPickedSuccessor = new Object[k]; + var remainingPicked = new Object[k]; + var remainingPickedSuccessor = new Object[k]; - List pickedValueList = new ArrayList<>(); - for (int i = 1; i < k + 1; i++) { + var pickedValueList = new ArrayList<>(); + for (var i = 1; i < k + 1; i++) { int index = entityToOffset.get(entities[i]); - Object value = entityToList.get(entities[i]).get(index); - Object valueSuccessor = entityToList.get(entities[i]).get(index + 1); + var value = entityToList.get(entities[i]).get(index); + var valueSuccessor = entityToList.get(entities[i]).get(index + 1); entityToOffset.merge(entities[i], 3, Integer::sum); - pickedValueList.add(0, value); + pickedValueList.addFirst(value); if (i != 1) { remainingPicked[remainingPicked.length - i] = value; @@ -286,16 +285,16 @@ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockDat } } - int finalValueIndex = entityToOffset.get(entities[k]); - List distinctEntityList = Arrays.stream(entities).distinct().collect(Collectors.toList()); - int entityListIndex = distinctEntityList.indexOf(entities[k]); + var finalValueIndex = entityToOffset.get(entities[k]); + var distinctEntityList = Arrays.stream(entities).distinct().toList(); + var entityListIndex = distinctEntityList.indexOf(entities[k]); when(mocks.workingRandom.ints(0, distinctEntityList.size())) .thenReturn(IntStream.iterate(entityListIndex, value -> value)); when(mocks.workingRandom.nextInt(entityToList.get(distinctEntityList.get(entityListIndex)).size())) .thenReturn(finalValueIndex); when(mocks.valueSelector.iterator()).thenReturn(pickedValueList.iterator()); - KOptMoveInfo out = new KOptMoveInfo(); + var out = new KOptMoveInfo(); out.removedEdgeList = new ArrayList<>(2 * k); out.entityList = Arrays.stream(entities).distinct().collect(Collectors.toList()); @@ -303,7 +302,7 @@ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockDat out.removedEdgeList.add(firstPicked); out.removedEdgeList.add(firstSuccessor); - for (int i = 0; i < remainingPicked.length; i++) { + for (var i = 0; i < remainingPicked.length; i++) { out.removedEdgeList.add(remainingPicked[i]); out.removedEdgeList.add(remainingPickedSuccessor[i]); } @@ -336,41 +335,45 @@ private KOptMoveInfo setupValidNonsequential4OptMove(KOptListMoveIteratorMockDat @Test void testSequentialKOptOnSameEntity() { - KOptListMoveIteratorMockData mocks = createMockKOptListMoveIterator(2, 5, new int[] { 1, 1, 1, 1 }); + var mocks = createMockKOptListMoveIterator(2, 5, new int[] { 1, 1, 1, 1 }); - KOptMoveInfo kOptMoveInfo = setupValidOddSequentialKOptMove(mocks, 3, "e1", "e1", "e1"); - KOptListMove kOptListMove = (KOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); + var kOptMoveInfo = setupValidOddSequentialKOptMove(mocks, 3, "e1", "e1", "e1"); + var kOptListMove = + (SelectorBasedKOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); kOptMoveInfo.verify(kOptListMove); kOptMoveInfo = setupValidOddSequentialKOptMove(mocks, 5, "e1", "e1", "e1", "e1", "e1"); - kOptListMove = (KOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); + kOptListMove = (SelectorBasedKOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); kOptMoveInfo.verify(kOptListMove); } @Test void testSequentialKOptOnDifferentEntities() { - KOptListMoveIteratorMockData mocks = createMockKOptListMoveIterator(2, 5, new int[] { 1, 1, 1, 1 }); + var mocks = createMockKOptListMoveIterator(2, 5, new int[] { 1, 1, 1, 1 }); - KOptMoveInfo kOptMoveInfo = setupValidOddSequentialKOptMove(mocks, 5, "e1", "e2", "e1", "e2", "e1"); - KOptListMove kOptListMove = (KOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); + var kOptMoveInfo = setupValidOddSequentialKOptMove(mocks, 5, "e1", "e2", "e1", "e2", "e1"); + var kOptListMove = + (SelectorBasedKOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); kOptMoveInfo.verify(kOptListMove); } @Test void testNonsequentialKOptOnSameEntity() { - KOptListMoveIteratorMockData mocks = createMockKOptListMoveIterator(2, 6, new int[] { 1, 1, 1, 1 }); + var mocks = createMockKOptListMoveIterator(2, 6, new int[] { 1, 1, 1, 1 }); - KOptMoveInfo kOptMoveInfo = setupValidNonsequential4OptMove(mocks, "e1", "e1", "e1", "e1", "e1"); - KOptListMove kOptListMove = (KOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); + var kOptMoveInfo = setupValidNonsequential4OptMove(mocks, "e1", "e1", "e1", "e1", "e1"); + var kOptListMove = + (SelectorBasedKOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); kOptMoveInfo.verify(kOptListMove); } @Test void testNonsequentialKOptOnDifferentEntity() { - KOptListMoveIteratorMockData mocks = createMockKOptListMoveIterator(2, 6, new int[] { 1, 1, 1, 1 }); + var mocks = createMockKOptListMoveIterator(2, 6, new int[] { 1, 1, 1, 1 }); - KOptMoveInfo kOptMoveInfo = setupValidNonsequential4OptMove(mocks, "e1", "e2", "e1", "e2", "e1"); - KOptListMove kOptListMove = (KOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); + var kOptMoveInfo = setupValidNonsequential4OptMove(mocks, "e1", "e2", "e1", "e2", "e1"); + var kOptListMove = + (SelectorBasedKOptListMove) mocks.kOptListMoveIterator.createUpcomingSelection(); kOptMoveInfo.verify(kOptListMove); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMoveTest.java similarity index 97% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMoveTest.java index 6da4afb4117..19d84bb17ac 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMoveTest.java @@ -32,7 +32,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -class KOptListMoveTest { +class SelectorBasedKOptListMoveTest { private final ListVariableDescriptor variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); @@ -98,7 +98,7 @@ void test3Opt() { v4, v6)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v5, v6, v4, v3); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 6); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 6); @@ -132,7 +132,7 @@ void test3OptPinned() { v5, v7)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v3, v6, v7, v5, v4); verify(scoreDirector).beforeListVariableChanged(variableDescriptorSpy, e1, 1, 7); verify(scoreDirector).afterListVariableChanged(variableDescriptorSpy, e1, 1, 7); @@ -158,7 +158,7 @@ void test3OptRebase() { v2, v5, v4, v6)); - InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( + var destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { { v1, destinationV1 }, { v2, destinationV2 }, @@ -175,9 +175,9 @@ void test3OptRebase() { when(supplyManager.demand(Mockito.any(ListVariableStateDemand.class))).thenReturn(inverseVariableSupply); when(inverseVariableSupply.getInverseSingleton(destinationE1.getValueList().get(0))).thenReturn(destinationE1); - var rebasedMove = kOptListMove.rebase(destinationScoreDirector); + var rebasedMove = kOptListMove.rebase(destinationScoreDirector.getMoveDirector()); assertThat(rebasedMove.isMoveDoable(destinationScoreDirector)).isTrue(); - rebasedMove.doMoveOnly(destinationScoreDirector); + rebasedMove.execute(destinationScoreDirector); assertThat(destinationE1.getValueList()).containsExactly(destinationV1, destinationV2, destinationV5, destinationV6, destinationV4, destinationV3); verify(destinationScoreDirector).beforeListVariableChanged(variableDescriptor, destinationE1, 0, 6); @@ -226,10 +226,10 @@ void testMultiEntity3OptRebase() { when(inverseVariableSupply.getInverseSingleton(destinationE1.getValueList().get(0))).thenReturn(destinationE1); when(inverseVariableSupply.getInverseSingleton(destinationE2.getValueList().get(0))).thenReturn(destinationE2); - var rebasedMove = kOptListMove.rebase(destinationScoreDirector); + var rebasedMove = kOptListMove.rebase(destinationScoreDirector.getMoveDirector()); assertThat(rebasedMove.isMoveDoable(destinationScoreDirector)).isTrue(); - rebasedMove.doMoveOnly(destinationScoreDirector); + rebasedMove.execute(destinationScoreDirector); assertThat(destinationE1.getValueList()).containsExactly(destinationV1, destinationV5); assertThat(destinationE2.getValueList()).containsExactly(destinationV2, destinationV6, destinationV4, destinationV3); verify(destinationScoreDirector).beforeListVariableChanged(variableDescriptor, destinationE1, 0, 4); @@ -256,7 +256,7 @@ void testMultiEntity2Opt() { v3, v7)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v6, v5, v4); assertThat(e2.getValueList()).containsExactly(v3, v7, v8); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 4); @@ -285,7 +285,7 @@ void testMultiEntity3Opt() { v4, v6)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v5); assertThat(e2.getValueList()).containsExactly(v2, v6, v4, v3); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 4); @@ -324,7 +324,7 @@ void testMultiEntity3OptPinned() { v4, v7)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v5); assertThat(e2.getValueList()).containsExactly(v3, v7, v4, v6); verify(scoreDirector).beforeListVariableChanged(variableDescriptorSpy, e1, 1, 5); @@ -356,7 +356,7 @@ void testMultiEntity4Opt() { v10, v8)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v9, v8); assertThat(e2.getValueList()).containsExactly(v10, v11, v3, v4); assertThat(e3.getValueList()).containsExactly(v5, v6, v7, v12); @@ -387,7 +387,7 @@ void test3OptLong() { v8, v10)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v3, v9, v10, v8, v7, v6, v5, v4); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 10); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 10); @@ -416,7 +416,7 @@ void test4Opt() { v5, v1)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v5, v4, v2, v3, v7, v6, v8); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 8); @@ -445,7 +445,7 @@ void test4OptLong() { v9, v12)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v3, v4, v5, v10, v11, v12, v9, v8, v7, v6, v2); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 12); @@ -476,7 +476,7 @@ void testInverted4OptLong() { v12, v3)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v5, v4, v3, v12, v11, v10, v6, v7, v8, v9); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 12); @@ -505,7 +505,7 @@ void testDoubleBridge4Opt() { v7, v2)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v7, v8, v5, v6, v3, v4); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 8); @@ -534,7 +534,7 @@ void testDoubleBridge4OptLong() { v9, v2)); assertThat(kOptListMove.isMoveDoable(scoreDirector)).isTrue(); - kOptListMove.doMoveOnly(scoreDirector); + kOptListMove.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v9, v10, v11, v12, v6, v7, v8, v3, v4, v5); @@ -639,7 +639,7 @@ void testEnableNearbyMixedModel() { * @param addedEdgeList The edges to add. Must contain only endpoints specified in the removedEdgeList. * @return A new sequential or non-sequential k-opt move with the specified undirected edges removed and added. */ - private static KOptListMove fromRemovedAndAddedEdges( + private static SelectorBasedKOptListMove fromRemovedAndAddedEdges( InnerScoreDirector scoreDirector, ListVariableDescriptor listVariableDescriptor, List removedEdgeList, @@ -648,7 +648,7 @@ private static KOptListMove fromRemovedAndAddedEd addedEdgeList); } - private static KOptListMove fromRemovedAndAddedEdges( + private static SelectorBasedKOptListMove fromRemovedAndAddedEdges( InnerScoreDirector scoreDirector, ListVariableDescriptor listVariableDescriptor, ListVariableDescriptor listVariableDescriptorSpy, @@ -716,7 +716,7 @@ private static KOptListMove fromRemovedAndAddedEd private static Function getSuccessorFunction(ListVariableDescriptor listVariableDescriptor, ListVariableStateSupply listVariableStateSupply) { - return (node) -> { + return node -> { var entity = listVariableStateSupply.getInverseSingleton(node); var valueList = (List) listVariableDescriptor.getValue(entity); var index = listVariableStateSupply.getIndex(node); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMoveTest.java similarity index 55% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMoveTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMoveTest.java index bb448086fd6..04df6843317 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/TwoOptListMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMoveTest.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -class TwoOptListMoveTest { +class SelectorBasedTwoOptListMoveTest { private final ListVariableDescriptor variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList(); @@ -46,15 +46,15 @@ void setUp() { @Test void doMove() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v5, v4, v3, v6, v7, v8); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var e1 = new TestdataListEntity("e1", v1, v2, v5, v4, v3, v6, v7, v8); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7, v8)); @@ -62,51 +62,44 @@ void doMove() { scoreDirector.setWorkingSolution(solution); // 2-Opt((v2, v5), (v3, v6)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 5); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 5); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v3, v4, v5, v6, v7, v8); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 2, 5); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 2, 5); - verify(scoreDirector).triggerVariableListeners(); } @Test void isMoveDoable() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v5, v4, v3, v6, v7, v8); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var e1 = new TestdataListEntity("e1", v1, v2, v5, v4, v3, v6, v7, v8); // 2-Opt((v2, v5), (v3, v6)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 5); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 5); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); // 2-Opt((v2, v3), (v2, v3)) - move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 2); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 2); assertThat(move.isMoveDoable(scoreDirector)).isFalse(); // 2-Opt((v2, v3), (v3, v4)) - move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 3); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 3); assertThat(move.isMoveDoable(scoreDirector)).isFalse(); // 2-Opt((v2, v3), (v4, v5)) - move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 4); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 4); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); // 2-Opt((v2, v3), (v1, v2)) - move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 1); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 1); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); } @@ -125,57 +118,57 @@ void isMoveDoableValueRangeProviderOnEntity() { valueRangeManager.reset(solution); // different entity => valid left and right - assertThat(new TwoOptListMove<>(otherVariableDescriptor, e1, e2, 1, 1).isMoveDoable(otherInnerScoreDirector)) + assertThat( + new SelectorBasedTwoOptListMove<>(otherVariableDescriptor, e1, e2, 1, 1).isMoveDoable(otherInnerScoreDirector)) .isTrue(); // different entity => invalid left - assertThat(new TwoOptListMove<>(otherVariableDescriptor, e1, e2, 0, 1).isMoveDoable(otherInnerScoreDirector)) + assertThat( + new SelectorBasedTwoOptListMove<>(otherVariableDescriptor, e1, e2, 0, 1).isMoveDoable(otherInnerScoreDirector)) .isFalse(); // different entity => invalid right - assertThat(new TwoOptListMove<>(otherVariableDescriptor, e1, e2, 1, 0).isMoveDoable(otherInnerScoreDirector)) + assertThat( + new SelectorBasedTwoOptListMove<>(otherVariableDescriptor, e1, e2, 1, 0).isMoveDoable(otherInnerScoreDirector)) .isFalse(); } @Test void isMoveDoableTailSwap() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListValue v9 = new TestdataListValue("9"); - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5, v6, v7, v8, v9); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var v9 = new TestdataListValue("9"); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5, v6, v7, v8, v9); // 2-Opt((v2, v3), (v6, v7)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e2, 2, 2); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e2, 2, 2); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); - move = new TwoOptListMove<>(variableDescriptor, - e1, e2, 1, 2); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e2, 1, 2); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); - move = new TwoOptListMove<>(variableDescriptor, - e1, e2, 2, 1); + move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e2, 2, 1); assertThat(move.isMoveDoable(scoreDirector)).isTrue(); } @Test void doTailSwap() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListValue v9 = new TestdataListValue("9"); - TestdataListEntity e1 = new TestdataListEntity("e1", v1, v2, v3, v4); - TestdataListEntity e2 = new TestdataListEntity("e2", v5, v6, v7, v8, v9); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var v9 = new TestdataListValue("9"); + var e1 = new TestdataListEntity("e1", v1, v2, v3, v4); + var e2 = new TestdataListEntity("e2", v5, v6, v7, v8, v9); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1, e2)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7, v8, v9)); @@ -183,9 +176,8 @@ void doTailSwap() { scoreDirector.setWorkingSolution(solution); // 2-Opt((v2, v3), (v6, v7)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e2, 2, 2); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e2, 2, 2); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v1, v2, v7, v8, v9); assertThat(e2.getValueList()).containsExactly(v5, v6, v3, v4); @@ -193,20 +185,19 @@ void doTailSwap() { verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 2, 5); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e2, 2, 5); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e2, 2, 4); - verify(scoreDirector).triggerVariableListeners(); } @Test void doMoveSecondEndsBeforeFirst() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListEntity e1 = new TestdataListEntity("e1", v8, v7, v3, v4, v5, v6, v2, v1); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var e1 = new TestdataListEntity("e1", v8, v7, v3, v4, v5, v6, v2, v1); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7, v8)); @@ -214,26 +205,24 @@ void doMoveSecondEndsBeforeFirst() { scoreDirector.setWorkingSolution(solution); // 2-Opt((v6, v2), (v7, v3)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 6, 2); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 6, 2); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v8, v1, v2, v3, v4, v5, v6, v7); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 8); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 8); - verify(scoreDirector).triggerVariableListeners(); } @Test void doMoveSecondEndsBeforeFirstUnbalanced() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListEntity e1 = new TestdataListEntity("e1", v5, v2, v3, v4, v1, v7, v6); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var e1 = new TestdataListEntity("e1", v5, v2, v3, v4, v1, v7, v6); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7)); @@ -241,26 +230,24 @@ void doMoveSecondEndsBeforeFirstUnbalanced() { scoreDirector.setWorkingSolution(solution); // 2-Opt((v4, v1), (v5, v2)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 4, 1); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 4, 1); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v5, v6, v7, v1, v2, v3, v4); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 7); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 7); - verify(scoreDirector).triggerVariableListeners(); } @Test void doMoveFirstEndsBeforeSecondUnbalanced() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListEntity e1 = new TestdataListEntity("e1", v2, v1, v7, v4, v5, v6, v3); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var e1 = new TestdataListEntity("e1", v2, v1, v7, v4, v5, v6, v3); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7)); @@ -268,27 +255,25 @@ void doMoveFirstEndsBeforeSecondUnbalanced() { scoreDirector.setWorkingSolution(solution); // 2-Opt((v4, v1), (v5, v2)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptor, - e1, e1, 2, 1); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 2, 1); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v2, v3, v6, v5, v4, v7, v1); verify(scoreDirector).beforeListVariableChanged(variableDescriptor, e1, 0, 7); verify(scoreDirector).afterListVariableChanged(variableDescriptor, e1, 0, 7); - verify(scoreDirector).triggerVariableListeners(); } @Test void doMoveSecondEndsBeforeFirstPinned() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListValue v5 = new TestdataListValue("5"); - TestdataListValue v6 = new TestdataListValue("6"); - TestdataListValue v7 = new TestdataListValue("7"); - TestdataListValue v8 = new TestdataListValue("8"); - TestdataListEntity e1 = new TestdataListEntity("e1", v8, v7, v3, v4, v5, v6, v2, v1); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var v3 = new TestdataListValue("3"); + var v4 = new TestdataListValue("4"); + var v5 = new TestdataListValue("5"); + var v6 = new TestdataListValue("6"); + var v7 = new TestdataListValue("7"); + var v8 = new TestdataListValue("8"); + var e1 = new TestdataListEntity("e1", v8, v7, v3, v4, v5, v6, v2, v1); var solution = new TestdataListSolution(); solution.setEntityList(List.of(e1)); solution.setValueList(List.of(v1, v2, v5, v4, v3, v6, v7, v8)); @@ -303,25 +288,23 @@ void doMoveSecondEndsBeforeFirstPinned() { Mockito.when(entityDescriptor.supportsPinning()).thenReturn(true); // 2-Opt((v6, v2), (v7, v3)) - TwoOptListMove move = new TwoOptListMove<>(variableDescriptorSpy, - e1, e1, 6, 2); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedTwoOptListMove<>(variableDescriptorSpy, e1, e1, 6, 2); + move.execute(scoreDirector); assertThat(e1.getValueList()).containsExactly(v8, v2, v3, v4, v5, v6, v7, v1); verify(scoreDirector).beforeListVariableChanged(variableDescriptorSpy, e1, 1, 8); verify(scoreDirector).afterListVariableChanged(variableDescriptorSpy, e1, 1, 8); - verify(scoreDirector).triggerVariableListeners(); } @Test void rebase() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListEntity e1 = new TestdataListEntity("e1"); + var v1 = new TestdataListValue("1"); + var v2 = new TestdataListValue("2"); + var e1 = new TestdataListEntity("e1"); - TestdataListValue destinationV1 = new TestdataListValue("1"); - TestdataListValue destinationV2 = new TestdataListValue("2"); - TestdataListEntity destinationE1 = new TestdataListEntity("e1"); + var destinationV1 = new TestdataListValue("1"); + var destinationV2 = new TestdataListValue("2"); + var destinationE1 = new TestdataListEntity("e1"); InnerScoreDirector destinationScoreDirector = mockRebasingScoreDirector( variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] { @@ -333,11 +316,12 @@ void rebase() { assertSameProperties( destinationE1, 0, 1, - new TwoOptListMove<>(variableDescriptor, e1, e1, 0, 1) - .rebase(destinationScoreDirector)); + new SelectorBasedTwoOptListMove<>(variableDescriptor, e1, e1, 0, 1) + .rebase(destinationScoreDirector.getMoveDirector())); } - static void assertSameProperties(Object destinationEntity, int destinationV1, int destinationV2, TwoOptListMove move) { + static void assertSameProperties(Object destinationEntity, int destinationV1, int destinationV2, + SelectorBasedTwoOptListMove move) { assertThat(move.getFirstEntity()).isSameAs(destinationEntity); assertThat(move.getFirstEdgeEndpoint()).isEqualTo(destinationV1); assertThat(move.getSecondEdgeEndpoint()).isEqualTo(destinationV2); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/localsearch/decider/forager/AcceptedLocalSearchForagerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/localsearch/decider/forager/AcceptedLocalSearchForagerTest.java index df194c5c38b..7805e9c68bc 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/localsearch/decider/forager/AcceptedLocalSearchForagerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/localsearch/decider/forager/AcceptedLocalSearchForagerTest.java @@ -10,7 +10,7 @@ import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.config.localsearch.decider.forager.LocalSearchPickEarlyType; import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.localsearch.decider.forager.finalist.HighestScoreFinalistPodium; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope; @@ -231,7 +231,7 @@ private static LocalSearchPhaseScope createPhaseScope() { private static LocalSearchMoveScope createMoveScope(LocalSearchStepScope stepScope, SimpleScore score, boolean accepted) { LocalSearchMoveScope moveScope = - new LocalSearchMoveScope<>(stepScope, 0, new DummyMove()); + new LocalSearchMoveScope<>(stepScope, 0, new SelectorBasedDummyMove()); moveScope.setInitializedScore(score); moveScope.setAccepted(accepted); return moveScope; 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 d3ad1f52a0d..e3501971e8f 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 @@ -9,6 +9,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -26,9 +27,9 @@ import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhase; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListAssignMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin.ListRuinRecreateMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedRuinRecreateMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListAssignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin.SelectorBasedListRuinRecreateMove; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirector; @@ -915,17 +916,17 @@ void undoNestedPhaseMove() { when(constructionHeuristicPhase.getMissingUpdatedElementsMap()).thenReturn(Map.of(e2, List.of(v2))); var ephemeralMoveDirector = moveDirector.ephemeral(); - var scoreDirector = ephemeralMoveDirector.getScoreDirector(); - var move = new ListRuinRecreateMove(listVariableDescriptor, - ruinRecreateConstructionHeuristicPhaseBuilder, new SolverScope<>(), List.of(v1), Set.of(e1)); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedListRuinRecreateMove(listVariableDescriptor, + ruinRecreateConstructionHeuristicPhaseBuilder, new SolverScope<>(), List.of(v1), + new LinkedHashSet<>(Set.of(e1))); + move.execute(ephemeralMoveDirector); var undoMove = (RecordedUndoMove) ephemeralMoveDirector.createUndoMove(); // e1 must be analyzed at the beginning of the move execution assertThat(undoMove.variableChangeActionList().stream().anyMatch(action -> { if (action instanceof ListVariableBeforeChangeAction beforeChangeAction) { return beforeChangeAction.entity() == e1 && beforeChangeAction.fromIndex() == 0 && beforeChangeAction.toIndex() == 1 && beforeChangeAction.oldValue().size() == 1 - && beforeChangeAction.oldValue().get(0).equals(v1); + && beforeChangeAction.oldValue().getFirst().equals(v1); } return false; })).isTrue(); @@ -935,7 +936,7 @@ void undoNestedPhaseMove() { if (action instanceof ListVariableBeforeChangeAction beforeChangeAction) { return beforeChangeAction.entity() == e2 && beforeChangeAction.fromIndex() == 0 && beforeChangeAction.toIndex() == 1 && beforeChangeAction.oldValue().size() == 1 - && beforeChangeAction.oldValue().get(0).equals(v2); + && beforeChangeAction.oldValue().getFirst().equals(v2); } return false; })).isTrue(); @@ -968,11 +969,9 @@ void testSolverScopeNestedPhase() { .thenReturn(ruinRecreateConstructionHeuristicPhaseBuilder); when(ruinRecreateConstructionHeuristicPhaseBuilder.build()).thenReturn(constructionHeuristicPhase); var ephemeralMoveDirector = moveDirector.ephemeral(); - var scoreDirector = ephemeralMoveDirector.getScoreDirector(); - - var move = new RuinRecreateMove(genuineVariableDescriptor, - ruinRecreateConstructionHeuristicPhaseBuilder, mainSolverScope, List.of(v1), Set.of(e1)); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedRuinRecreateMove(genuineVariableDescriptor, + ruinRecreateConstructionHeuristicPhaseBuilder, mainSolverScope, List.of(v1), new LinkedHashSet<>(Set.of(e1))); + move.execute(ephemeralMoveDirector); // Not using the main solver scope verify(constructionHeuristicPhase, times(0)).solve(mainSolverScope); // Uses a new instance of SolverScope @@ -1000,10 +999,9 @@ void undoCascadingUpdateShadowVariable() { assertThat(workingSolution.getValueList()).map(TestdataSingleCascadingValue::getCascadeValue).allMatch(Objects::isNull); var ephemeralMoveDirector = moveDirector.ephemeral(); - var scoreDirector = ephemeralMoveDirector.getScoreDirector(); - var move = - new ListAssignMove<>(TestdataSingleCascadingEntity.buildVariableDescriptorForValueList(), valueA, entityA, 0); - move.doMoveOnly(scoreDirector); + var move = new SelectorBasedListAssignMove<>(TestdataSingleCascadingEntity.buildVariableDescriptorForValueList(), + valueA, entityA, 0); + move.execute(ephemeralMoveDirector); assertThat(valueA.getCascadeValue()).isNotNull(); ephemeralMoveDirector.close(); 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 7c79acbfe64..04ec2fc44be 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 @@ -66,11 +66,12 @@ 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.move.AbstractSelectorBasedMove; 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; +import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.Pair; import ai.timefold.solver.core.preview.api.neighborhood.Neighborhood; import ai.timefold.solver.core.preview.api.neighborhood.NeighborhoodBuilder; @@ -208,9 +209,9 @@ void solveWithDefaultNeighborhoodProviderListVar() { // Both values are on the same entity; the goal of the solver is to move one of them to the other entity. var solution = TestdataListSolution.generateUninitializedSolution(2, 2); - var v1 = solution.getValueList().get(0); + var v1 = solution.getValueList().getFirst(); var v2 = solution.getValueList().get(1); - var e1 = solution.getEntityList().get(0); + var e1 = solution.getEntityList().getFirst(); e1.addValue(v1); e1.addValue(v2); SolutionManager.updateShadowVariables(solution); @@ -231,12 +232,12 @@ void solveWithDefaultNeighborhoodProviderMixedModel() { var solution = TestdataMixedSolution.generateUninitializedSolution(2, 2, 2); // Values are assigned in reverse; the solver needs to swap them. - var e1 = solution.getEntityList().get(0); + var e1 = solution.getEntityList().getFirst(); var e2 = solution.getEntityList().get(1); e1.setBasicValue(solution.getOtherValueList().get(1)); - e2.setBasicValue(solution.getOtherValueList().get(0)); + e2.setBasicValue(solution.getOtherValueList().getFirst()); // Both values are on the same entity; the goal of the solver is to move one of them to the other entity. - var v1 = solution.getValueList().get(0); + var v1 = solution.getValueList().getFirst(); var v2 = solution.getValueList().get(1); e1.setValueList(new ArrayList<>(List.of(v1, v2))); SolutionManager.updateShadowVariables(solution); @@ -322,8 +323,8 @@ void solveWithDefaultNeighborhoodProviderMultiEntity() { // Set each value to be the same, so that the solver has to split them. var solution = TestdataMultiEntitySolution.generateUninitializedSolution(2, 2); - solution.getLeadEntityList().forEach(e -> e.setValue(solution.getValueList().get(0))); - solution.getHerdEntityList().forEach(e -> e.setLeadEntity(solution.getLeadEntityList().get(0))); + solution.getLeadEntityList().forEach(e -> e.setValue(solution.getValueList().getFirst())); + solution.getHerdEntityList().forEach(e -> e.setLeadEntity(solution.getLeadEntityList().getFirst())); // Zero result means each value has different value. var result = PlannerTestUtils.solve(solverConfig, solution); @@ -559,8 +560,7 @@ void solveWithProblemChange() throws InterruptedException { } }); - var executorService = Executors.newSingleThreadExecutor(); - try { + try (var executorService = Executors.newSingleThreadExecutor()) { executorService.submit(() -> { solver.solve(solution); }); @@ -571,8 +571,6 @@ void solveWithProblemChange() throws InterruptedException { solutionWithProblemChangeReceived.await(); assertThat(bestSolution.get().getValueList()).hasSize(valueCount + 1); solver.terminateEarly(); - } finally { - executorService.shutdownNow(); } } @@ -668,7 +666,7 @@ void solveWithAllowsUnassignedValuesListVariable() { var bestSolution = PlannerTestUtils.solve(solverConfig, solution); assertSoftly(softly -> { softly.assertThat(bestSolution.getScore()).isEqualTo(SimpleScore.of(0)); // Nothing is assigned. - var firstEntity = bestSolution.getEntityList().get(0); + var firstEntity = bestSolution.getEntityList().getFirst(); softly.assertThat(firstEntity.getValueList()).isEmpty(); }); @@ -700,7 +698,7 @@ void solveWithAllowsUnassignedValuesListVariableAndOnlyDown() { // Everything is assigned, even though ONLY_DOWN caused the CH to pick the first selected move. // Checks for a bug where NoChangeMove would be generated first, meaning nothing would get assigned. softly.assertThat(bestSolution.getScore()).isEqualTo(SimpleScore.of(4)); - var firstEntity = bestSolution.getEntityList().get(0); + var firstEntity = bestSolution.getEntityList().getFirst(); softly.assertThat(firstEntity.getValueList()).hasSize(4); }); @@ -746,9 +744,9 @@ void solveWithCHAllowsUnassignedValuesListVariableAndTerminateInStep() { void solveWithPlanningListVariableEntityPinFair() { var expectedValueCount = 4; var solution = TestdataPinnedListSolution.generateUninitializedSolution(expectedValueCount, 3); - var pinnedEntity = solution.getEntityList().get(0); + var pinnedEntity = solution.getEntityList().getFirst(); var pinnedList = pinnedEntity.getValueList(); - var pinnedValue = solution.getValueList().get(0); + var pinnedValue = solution.getValueList().getFirst(); pinnedList.add(pinnedValue); pinnedEntity.setPinned(true); @@ -763,8 +761,8 @@ void solveWithPlanningListVariableEntityPinFair() { assertThat(solution).isNotNull(); assertThat(solution.getScore()).isEqualTo(SimpleScore.ZERO); // No unused entities. - assertThat(solution.getEntityList().get(0).getValueList()) - .containsExactly(solution.getValueList().get(0)); + assertThat(solution.getEntityList().getFirst().getValueList()) + .containsExactly(solution.getValueList().getFirst()); var actualValueCount = solution.getEntityList().stream() .mapToInt(e -> e.getValueList().size()) .sum(); @@ -781,9 +779,9 @@ private static Solution_ updateSolution(SolverFactory sol void solveWithPlanningListVariableEntityPinUnfair() { var expectedValueCount = 4; var solution = TestdataPinnedListSolution.generateUninitializedSolution(expectedValueCount, 3); - var pinnedEntity = solution.getEntityList().get(0); + var pinnedEntity = solution.getEntityList().getFirst(); var pinnedList = pinnedEntity.getValueList(); - var pinnedValue = solution.getValueList().get(0); + var pinnedValue = solution.getValueList().getFirst(); pinnedList.add(pinnedValue); pinnedEntity.setPinned(true); @@ -799,8 +797,8 @@ void solveWithPlanningListVariableEntityPinUnfair() { assertThat(solution).isNotNull(); // 1 unused entity; out of 3 total, one is pinned and the other gets all the values. assertThat(solution.getScore()).isEqualTo(SimpleScore.of(1)); - assertThat(solution.getEntityList().get(0).getValueList()) - .containsExactly(solution.getValueList().get(0)); + assertThat(solution.getEntityList().getFirst().getValueList()) + .containsExactly(solution.getValueList().getFirst()); var actualValueCount = solution.getEntityList().stream() .mapToInt(e -> e.getValueList().size()) .sum(); @@ -812,9 +810,9 @@ void solveWithPlanningListVariablePinIndexFair() { var expectedValueCount = 4; var solution = TestdataPinnedWithIndexListSolution.generateUninitializedSolution(expectedValueCount, 3); // Pin the first list entirely. - var pinnedEntity = solution.getEntityList().get(0); + var pinnedEntity = solution.getEntityList().getFirst(); var pinnedList = pinnedEntity.getValueList(); - var pinnedValue = solution.getValueList().get(0); + var pinnedValue = solution.getValueList().getFirst(); pinnedList.add(pinnedValue); pinnedEntity.setPinned(true); // In the second list, pin only the first value. @@ -838,7 +836,7 @@ void solveWithPlanningListVariablePinIndexFair() { assertThat(solution).isNotNull(); assertThat(solution.getScore()).isEqualTo(SimpleScore.ZERO); // No unused entities. - assertThat(solution.getEntityList().get(0).getValueList()).containsExactly(solution.getValueList().get(0)); + assertThat(solution.getEntityList().getFirst().getValueList()).containsExactly(solution.getValueList().getFirst()); assertThat(solution.getEntityList().get(1).getValueList()) .first() .isEqualTo(solution.getValueList().get(1)); @@ -853,9 +851,9 @@ void solveWithPlanningListVariablePinIndexUnfair() { var expectedValueCount = 4; var solution = TestdataPinnedWithIndexListSolution.generateUninitializedSolution(expectedValueCount, 3); // Pin the first list entirely. - var pinnedEntity = solution.getEntityList().get(0); + var pinnedEntity = solution.getEntityList().getFirst(); var pinnedList = pinnedEntity.getValueList(); - var pinnedValue = solution.getValueList().get(0); + var pinnedValue = solution.getValueList().getFirst(); pinnedList.add(pinnedValue); pinnedEntity.setPinned(true); // In the second list, pin only the first value. @@ -880,7 +878,7 @@ void solveWithPlanningListVariablePinIndexUnfair() { assertThat(solution).isNotNull(); // 1 unused entity; out of 3 total, one is pinned and the other gets all the values. assertThat(solution.getScore()).isEqualTo(SimpleScore.of(1)); - assertThat(solution.getEntityList().get(0).getValueList()).containsExactly(solution.getValueList().get(0)); + assertThat(solution.getEntityList().getFirst().getValueList()).containsExactly(solution.getValueList().getFirst()); assertThat(solution.getEntityList().get(1).getValueList()) .containsExactlyInAnyOrder(solution.getValueList().get(1), solution.getValueList().get(2), @@ -917,7 +915,7 @@ void solveCustomConfigListVariable() { @ParameterizedTest @MethodSource("generateMovesForSingleVar") - void solveSingleVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + void solveSingleVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() @@ -934,8 +932,8 @@ void solveSingleVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { .doesNotThrowAnyException(); } - private static List generateMovesForSingleVar() { - var allMoveSelectionConfigList = new ArrayList(); + private static List> generateMovesForSingleVar() { + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); // Swap - basic @@ -953,7 +951,7 @@ private static List generateMovesForSingleVar() { @ParameterizedTest @MethodSource("generateMovesForListVar") - void solveListVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + void solveListVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() @@ -970,8 +968,8 @@ void solveListVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { .doesNotThrowAnyException(); } - private static List generateMovesForListVar() { - var allMoveSelectionConfigList = new ArrayList(); + private static List> generateMovesForListVar() { + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ListChangeMoveSelectorConfig()); // Swap - basic @@ -989,7 +987,7 @@ private static List generateMovesForListVar() { @ParameterizedTest @MethodSource("generateMovesForMultiVar") - void solveMultiVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + void solveMultiVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() @@ -1006,8 +1004,8 @@ void solveMultiVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { .doesNotThrowAnyException(); } - private static List generateMovesForMultiVar() { - var allMoveSelectionConfigList = new ArrayList(); + private static List> generateMovesForMultiVar() { + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); // Swap - basic @@ -1044,7 +1042,7 @@ void solveMultiEntity() { @ParameterizedTest @MethodSource("generateMovesForMultiEntity") - void solveMultiEntityMoveConfig(MoveSelectorConfig moveSelectionConfig) { + void solveMultiEntityMoveConfig(MoveSelectorConfig moveSelectionConfig) { // Construction Heuristic var leadConstructionHeuristicConfig = new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedEntityPlacerConfig() @@ -1072,8 +1070,8 @@ void solveMultiEntityMoveConfig(MoveSelectorConfig moveSelectionConfig) { assertThat(solution).isNotNull(); } - private static List generateMovesForMultiEntity() { - var allMoveSelectionConfigList = new ArrayList(); + private static List> generateMovesForMultiEntity() { + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); // Swap - basic @@ -1333,13 +1331,13 @@ void solvePinnedMixedModel() { var problem = TestdataMixedSolution.generateUninitializedSolution(3, 2, 2); // Pin the first entity - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setPinnedIndex(0); + problem.getEntityList().getFirst().setPinned(true); + problem.getEntityList().getFirst().setPinnedIndex(0); var solution = PlannerTestUtils.solve(solverConfig, problem); // The first entity should remain unchanged - assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getValueList()).isEmpty(); + assertThat(solution.getEntityList().getFirst().getBasicValue()).isNull(); + assertThat(solution.getEntityList().getFirst().getSecondBasicValue()).isNull(); + assertThat(solution.getEntityList().getFirst().getValueList()).isEmpty(); } @Test @@ -1352,9 +1350,9 @@ void solveUnassignedMixedModel() { var problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 2, 2); // Block values and make the basic and list variables unassigned - problem.getValueList().get(0).setBlocked(true); + problem.getValueList().getFirst().setBlocked(true); problem.getValueList().get(1).setBlocked(true); - problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().getFirst().setBlocked(true); problem.getOtherValueList().get(1).setBlocked(true); var solution = PlannerTestUtils.solve(solverConfig, problem); assertThat(solution.getEntityList().stream() @@ -1378,40 +1376,41 @@ void solvePinnedAndUnassignedMixedModel() { // Pin the entire first entity var problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 2, 2); - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); + problem.getEntityList().getFirst().setPinned(true); + problem.getEntityList().getFirst().setBasicValue(problem.getOtherValueList().getFirst()); + problem.getEntityList().getFirst().setSecondBasicValue(problem.getOtherValueList().getFirst()); + problem.getEntityList().getFirst().setValueList(List.of(problem.getValueList().getFirst())); // Block values and make the basic and list variables unassigned - problem.getValueList().get(0).setBlocked(true); + problem.getValueList().getFirst().setBlocked(true); problem.getValueList().get(1).setBlocked(true); - problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().getFirst().setBlocked(true); problem.getOtherValueList().get(1).setBlocked(true); var solution = PlannerTestUtils.solve(solverConfig, problem); // The first entity should remain unchanged - assertThat(solution.getEntityList().get(0).getBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(0).getValueList()).hasSize(1); + assertThat(solution.getEntityList().getFirst().getBasicValue()).isNotNull(); + assertThat(solution.getEntityList().getFirst().getSecondBasicValue()).isNotNull(); + assertThat(solution.getEntityList().getFirst().getValueList()).hasSize(1); assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); assertThat(solution.getEntityList().get(1).getValueList()).isEmpty(); // Pin partially the first entity list problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 4, 2); - problem.getEntityList().get(0).setPinnedIndex(2); - problem.getEntityList().get(0).setValueList(problem.getValueList().subList(1, 3)); + problem.getEntityList().getFirst().setPinnedIndex(2); + problem.getEntityList().getFirst().setValueList(problem.getValueList().subList(1, 3)); // Block values and make the basic variable unassigned - problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().getFirst().setBlocked(true); problem.getOtherValueList().get(1).setBlocked(true); solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); + assertThat(solution.getEntityList().getFirst().getBasicValue()).isNull(); + assertThat(solution.getEntityList().getFirst().getSecondBasicValue()).isNotNull(); // The pinning index fixed the values 1 and 2. The only remaining option is values are 0 and 3. // The score is bigger when the list size is 3 - assertThat(solution.getEntityList().get(0).getValueList()).hasSize(3); - assertThat(solution.getEntityList().get(0).getValueList()) + assertThat(solution.getEntityList().getFirst().getValueList()).hasSize(3); + assertThat(solution.getEntityList().getFirst().getValueList()) .hasSameElementsAs( - List.of(problem.getValueList().get(1), problem.getValueList().get(2), problem.getValueList().get(0))); + List.of(problem.getValueList().get(1), problem.getValueList().get(2), + problem.getValueList().getFirst())); assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); assertThat(solution.getEntityList().get(1).getValueList()).hasSize(1); @@ -1441,7 +1440,7 @@ void solveStaleBuiltinShadows() { var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().get(0).getValue().getCode()).isEqualTo("v1"); + assertThat(solution.getEntityList().getFirst().getValue().getCode()).isEqualTo("v1"); assertThat(solution.getEntityList().get(1).getValue().getCode()).isEqualTo("v2"); assertThat(solution.getScore()).isEqualTo(SimpleScore.of(-2)); @@ -1482,15 +1481,16 @@ void solveStaleDeclarativeShadows() { var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntities().get(0).getValues()).map(TestdataConcurrentValue::getId).containsExactly("a1", "b1"); + assertThat(solution.getEntities().getFirst().getValues()).map(TestdataConcurrentValue::getId).containsExactly("a1", + "b1"); assertThat(solution.getEntities().get(1).getValues()).map(TestdataConcurrentValue::getId).containsExactly("a2", "b2"); assertThat(solution.getScore()).isEqualTo(HardSoftScore.of(0, -240)); } - private static List generateMovesForMixedModel() { + private static List> generateMovesForMixedModel() { // Local Search - var allMoveSelectionConfigList = new ArrayList(); + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); // Swap - basic @@ -1529,7 +1529,7 @@ private static List generateMovesForMixedModel() { @ParameterizedTest @MethodSource("generateMovesForMixedModel") - void solveMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { + void solveMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() @@ -1552,9 +1552,9 @@ void solveMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { .isEmpty(); } - private static List generateMovesForMultiEntityMixedModel() { + private static List> generateMovesForMultiEntityMixedModel() { // Local Search - var allMoveSelectionConfigList = new ArrayList(); + var allMoveSelectionConfigList = new ArrayList>(); // Change - basic allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); // Swap - basic @@ -1595,7 +1595,7 @@ private static List generateMovesForMultiEntityMixedModel() @ParameterizedTest @MethodSource("generateMovesForMultiEntityMixedModel") - void solveMultiEntityMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { + void solveMultiEntityMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { // Construction Heuristic var constructionHeuristicValuePlacer = new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -1631,8 +1631,8 @@ void solveMultiEntityMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig } } - private static List generateMovesForBasicVar() { - var allMoveSelectionConfigList = new ArrayList(); + private static List> generateMovesForBasicVar() { + var allMoveSelectionConfigList = new ArrayList>(); // Shared entity selector config var entitySelectorConfig = new EntitySelectorConfig() .withEntityClass(TestdataAllowsUnassignedEntityProvidingEntity.class); @@ -1663,17 +1663,17 @@ private static List generateMovesForBasicVar() { @ParameterizedTest @MethodSource("generateMovesForBasicVar") - void solveBasicVarEntityRangeModelSingleLocalSearch(MoveSelectorConfig moveSelectionConfig) { + void solveBasicVarEntityRangeModelSingleLocalSearch(MoveSelectorConfig moveSelectionConfig) { solveBasicVarEntityRangeModel(moveSelectionConfig, false); } @ParameterizedTest @MethodSource("generateMovesForBasicVar") - void solveBasicVarEntityRangeModelMultipleLocalSearch(MoveSelectorConfig moveSelectionConfig) { + void solveBasicVarEntityRangeModelMultipleLocalSearch(MoveSelectorConfig moveSelectionConfig) { solveBasicVarEntityRangeModel(moveSelectionConfig, true); } - private void solveBasicVarEntityRangeModel(MoveSelectorConfig moveSelectionConfig, boolean multipleLocalSearch) { + private void solveBasicVarEntityRangeModel(MoveSelectorConfig moveSelectionConfig, boolean multipleLocalSearch) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() .withMoveSelectorConfig(moveSelectionConfig) @@ -1710,7 +1710,7 @@ private void solveBasicVarEntityRangeModel(MoveSelectorConfig moveSelectionConfi var bestSolution = PlannerTestUtils.solve(solverConfig, solution, true); assertThat(bestSolution).isNotNull(); - var bestEntity1 = bestSolution.getEntityList().get(0); + var bestEntity1 = bestSolution.getEntityList().getFirst(); assertThat(bestEntity1.getValue()).isNotIn(value4, value5); var bestEntity2 = bestSolution.getEntityList().get(1); assertThat(bestEntity2.getValue()).isNotIn(value3, value4); @@ -1718,9 +1718,9 @@ private void solveBasicVarEntityRangeModel(MoveSelectorConfig moveSelectionConfi assertThat(bestEntity3.getValue()).isNotIn(value1, value2, value3); } - private static List generateMovesForListVarEntityRangeModel() { + private static List> generateMovesForListVarEntityRangeModel() { // Local Search - var allMoveSelectionConfigList = new ArrayList(); + var allMoveSelectionConfigList = new ArrayList>(); // Shared value selector config var valueSelectorConfig = new ValueSelectorConfig() .withVariableName("valueList"); @@ -1757,17 +1757,17 @@ private static List generateMovesForListVarEntityRangeModel( @ParameterizedTest @MethodSource("generateMovesForListVarEntityRangeModel") - void solveListVarEntityRangeModelSingleLocalSearch(MoveSelectorConfig moveSelectionConfig) { + void solveListVarEntityRangeModelSingleLocalSearch(MoveSelectorConfig moveSelectionConfig) { solveListVarEntityRangeModel(moveSelectionConfig, false); } @ParameterizedTest @MethodSource("generateMovesForListVarEntityRangeModel") - void solveListVarEntityRangeModelMultipleLocalSearch(MoveSelectorConfig moveSelectionConfig) { + void solveListVarEntityRangeModelMultipleLocalSearch(MoveSelectorConfig moveSelectionConfig) { solveListVarEntityRangeModel(moveSelectionConfig, true); } - private void solveListVarEntityRangeModel(MoveSelectorConfig moveSelectionConfig, boolean multipleLocalSearch) { + private void solveListVarEntityRangeModel(MoveSelectorConfig moveSelectionConfig, boolean multipleLocalSearch) { // Local search var localSearchConfig = new LocalSearchPhaseConfig() .withMoveSelectorConfig(moveSelectionConfig) @@ -1804,7 +1804,7 @@ private void solveListVarEntityRangeModel(MoveSelectorConfig moveSelectionConfig var bestSolution = PlannerTestUtils.solve(solverConfig, solution, true); assertThat(bestSolution).isNotNull(); - var bestEntity1 = bestSolution.getEntityList().get(0); + var bestEntity1 = bestSolution.getEntityList().getFirst(); assertThat(bestEntity1.getValueList()).hasSizeGreaterThan(0); assertThat(bestEntity1.getValueList()).doesNotContain(value3, value4, value5); var bestEntity2 = bestSolution.getEntityList().get(1); @@ -2038,32 +2038,35 @@ public static final class MaximizeUnusedEntitiesEasyScoreCalculator @Override public @NonNull SimpleScore calculateScore(@NonNull Object solution) { - if (solution instanceof TestdataPinnedListSolution testdataPinnedListSolution) { - var unusedEntities = 0; - for (var entity : testdataPinnedListSolution.getEntityList()) { - if (entity.getValueList().isEmpty()) { - unusedEntities++; + switch (solution) { + case TestdataPinnedListSolution testdataPinnedListSolution -> { + var unusedEntities = 0; + for (var entity : testdataPinnedListSolution.getEntityList()) { + if (entity.getValueList().isEmpty()) { + unusedEntities++; + } } + return SimpleScore.of(unusedEntities); } - return SimpleScore.of(unusedEntities); - } else if (solution instanceof TestdataPinnedWithIndexListSolution testdataPinnedWithIndexListSolution) { - var unusedEntities = 0; - for (var entity : testdataPinnedWithIndexListSolution.getEntityList()) { - if (entity.getValueList().isEmpty()) { - unusedEntities++; + case TestdataPinnedWithIndexListSolution testdataPinnedWithIndexListSolution -> { + var unusedEntities = 0; + for (var entity : testdataPinnedWithIndexListSolution.getEntityList()) { + if (entity.getValueList().isEmpty()) { + unusedEntities++; + } } + return SimpleScore.of(unusedEntities); } - return SimpleScore.of(unusedEntities); - } else if (solution instanceof TestdataAllowsUnassignedValuesListSolution testdataAllowsUnassignedValuesListSolution) { - var unusedEntities = 0; - for (var entity : testdataAllowsUnassignedValuesListSolution.getEntityList()) { - if (entity.getValueList().isEmpty()) { - unusedEntities++; + case TestdataAllowsUnassignedValuesListSolution testdataAllowsUnassignedValuesListSolution -> { + var unusedEntities = 0; + for (var entity : testdataAllowsUnassignedValuesListSolution.getEntityList()) { + if (entity.getValueList().isEmpty()) { + unusedEntities++; + } } + return SimpleScore.of(unusedEntities); } - return SimpleScore.of(unusedEntities); - } else { - throw new UnsupportedOperationException(); + default -> throw new UnsupportedOperationException(); } } } @@ -2153,7 +2156,7 @@ public static final class DummyEasyScoreCalculator implements EasyScoreCalculato @Override public @NonNull SimpleScore calculateScore(@NonNull TestdataSolution solution) { var valueList = solution.getValueList(); - var firstValue = valueList.get(0); + var firstValue = valueList.getFirst(); var valueSet = new HashSet(valueList.size()); solution.getEntityList().forEach(e -> { if (e.getValue() == firstValue) { @@ -2170,9 +2173,9 @@ public static final class DummyMultiVarEasyScoreCalculator @Override public @NonNull SimpleScore calculateScore(@NonNull TestdataMultiVarSolution solution) { - var primaryValue = solution.getValueList().get(0); + var primaryValue = solution.getValueList().getFirst(); var secondaryValue = solution.getValueList().get(1); - var otherValue = solution.getOtherValueList().get(0); + var otherValue = solution.getOtherValueList().getFirst(); var valueSet = new HashSet>(); solution.getMultiVarEntityList().forEach(e -> { if (e.getPrimaryValue() == primaryValue) { @@ -2195,8 +2198,8 @@ public static final class DummyMultiEntityEasyScoreCalculator @Override public @NonNull SimpleScore calculateScore(@NonNull TestdataMultiEntitySolution solution) { - var primaryValue = solution.getValueList().get(0); - var secondaryValue = solution.getLeadEntityList().get(0); + var primaryValue = solution.getValueList().getFirst(); + var secondaryValue = solution.getLeadEntityList().getFirst(); var valueSet = new HashSet<>(); solution.getLeadEntityList().forEach(e -> { if (e.getValue() == primaryValue) { @@ -2239,7 +2242,7 @@ public static final class TestingMixedEasyScoreCalculator @Override public @NonNull SimpleScore calculateScore(@NonNull TestdataMixedSolution testdataSolution) { - var firstValue = testdataSolution.getOtherValueList().get(0); + var firstValue = testdataSolution.getOtherValueList().getFirst(); var secondValue = testdataSolution.getOtherValueList().get(1); var sum = new LongAdder(); testdataSolution.getEntityList().forEach(e -> { @@ -2265,46 +2268,42 @@ public static final class InvalidCustomPhaseCommand implements PhaseCommand scoreDirector, BooleanSupplier isPhaseTerminated) { - var entity = scoreDirector.getWorkingSolution().getEntityList().get(0); + var entity = scoreDirector.getWorkingSolution().getEntityList().getFirst(); 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 { + public static final class InvalidMoveListFactory + implements MoveIteratorFactory { @Override public long getSize(ScoreDirector scoreDirector) { return 1; } @Override - public Iterator + public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { - return List.of(new InvalidMove()).iterator(); + return List.of(new InvalidSelectorBasedMove()).iterator(); } @Override - public Iterator createRandomMoveIterator( + public Iterator createRandomMoveIterator( ScoreDirector scoreDirector, RandomGenerator workingRandom) { return createOriginalMoveIterator(scoreDirector); } } - public static final class InvalidMove extends AbstractMove { + public static final class InvalidSelectorBasedMove extends AbstractSelectorBasedMove { @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - var entity = scoreDirector.getWorkingSolution().getEntityList().get(0); + protected void execute(VariableDescriptorAwareScoreDirector scoreDirector) { + var entity = scoreDirector.getWorkingSolution().getEntityList().getFirst(); 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/impl/solver/SolverMetricsIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java index 092b4faea98..5c39f18ea9c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java @@ -40,7 +40,7 @@ import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; @@ -379,10 +379,10 @@ public static class BestScoreMetricEasyScoreCalculator } public static class NoneValueSelectionFilter - implements SelectionFilter> { + implements SelectionFilter> { @Override public boolean accept(ScoreDirector scoreDirector, - ChangeMove selection) { + SelectorBasedChangeMove selection) { return ((TestdataValue) (selection.getToPlanningValue())).getCode().equals("none"); } } diff --git a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java index a738e5bb6d1..bc247e5025a 100644 --- a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java +++ b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java @@ -1,8 +1,8 @@ package ai.timefold.solver.core.preview.api.move.builtin; -import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.SequencedCollection; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; @@ -45,12 +45,12 @@ public Move rebase(Rebaser rebaser) { } @Override - public Collection getPlanningEntities() { + public SequencedCollection getPlanningEntities() { return Collections.emptyList(); } @Override - public Collection getPlanningValues() { + public SequencedCollection getPlanningValues() { return Collections.emptyList(); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java index 4af8e0d7930..bb8949e6350 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java @@ -6,31 +6,33 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; public class MixedCustomMoveIteratorFactory - implements MoveIteratorFactory> { + implements MoveIteratorFactory> { @Override public long getSize(ScoreDirector scoreDirector) { return scoreDirector.getWorkingSolution().getEntityList().size(); } @Override - public Iterator> + public Iterator> createOriginalMoveIterator(ScoreDirector scoreDirector) { throw new UnsupportedOperationException(); } @Override - public Iterator> + public Iterator> createRandomMoveIterator(ScoreDirector scoreDirector, RandomGenerator workingRandom) { var solutionDescriptor = TestdataMixedSolution.buildSolutionDescriptor(); var variableDescriptor = solutionDescriptor.findEntityDescriptor(TestdataMixedEntity.class).getGenuineVariableDescriptor("basicValue"); - var move1 = new ChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().get(0), - scoreDirector.getWorkingSolution().getOtherValueList().get(0)); - var move2 = new ChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().get(0), - scoreDirector.getWorkingSolution().getOtherValueList().get(1)); + var move1 = + new SelectorBasedChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().getFirst(), + scoreDirector.getWorkingSolution().getOtherValueList().getFirst()); + var move2 = + new SelectorBasedChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().getFirst(), + scoreDirector.getWorkingSolution().getOtherValueList().get(1)); return Stream.of(move1, move2).iterator(); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java index e421db6f41a..4deb7bb0229 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java @@ -4,6 +4,7 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; public class MixedCustomPhaseCommand implements PhaseCommand { @@ -12,7 +13,7 @@ public void changeWorkingSolution(ScoreDirector scoreDire var moveIteratorFactory = new MixedCustomMoveIteratorFactory(); var moveIterator = moveIteratorFactory.createRandomMoveIterator(scoreDirector, null); var move = moveIterator.next(); - move.doMoveOnly(scoreDirector); + move.execute(((InnerScoreDirector) scoreDirector).getMoveDirector()); scoreDirector.triggerVariableListeners(); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testutil/CodeAssertable.java b/core/src/test/java/ai/timefold/solver/core/testutil/CodeAssertable.java index bfedf0135b1..cdbdf4abad3 100644 --- a/core/src/test/java/ai/timefold/solver/core/testutil/CodeAssertable.java +++ b/core/src/test/java/ai/timefold/solver/core/testutil/CodeAssertable.java @@ -3,22 +3,22 @@ import java.util.List; import java.util.Objects; -import ai.timefold.solver.core.impl.heuristic.move.CompositeMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.move.NoChangeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedCompositeMove; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedNoChangeMove; import ai.timefold.solver.core.impl.heuristic.selector.list.SubList; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.PillarChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SwapMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListAssignMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListSwapMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListUnassignMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SubListChangeMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SubListSwapMove; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SubListUnassignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedPillarChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedSwapMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListAssignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListSwapMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedListUnassignMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedSubListChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedSubListSwapMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.SelectorBasedSubListUnassignMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement; +import ai.timefold.solver.core.preview.api.move.Move; public interface CodeAssertable { @@ -26,89 +26,109 @@ public interface CodeAssertable { static CodeAssertable convert(Object o) { Objects.requireNonNull(o); - if (o instanceof CodeAssertable assertable) { - return assertable; - } else if (o instanceof NoChangeMove) { - return () -> "No change"; - } else if (o instanceof ChangeMove changeMove) { - final String code = convert(changeMove.getEntity()).getCode() - + "->" + convert(changeMove.getToPlanningValue()).getCode(); - return () -> code; - } else if (o instanceof SwapMove swapMove) { - final String code = convert(swapMove.getLeftEntity()).getCode() - + "<->" + convert(swapMove.getRightEntity()).getCode(); - return () -> code; - } else if (o instanceof PillarChangeMove pillarChangeMove) { - final String code = pillarChangeMove.getPillar() + - "->" + convert(pillarChangeMove.getToPlanningValue()).getCode(); - return () -> code; - } else if (o instanceof CompositeMove compositeMove) { - StringBuilder codeBuilder = new StringBuilder(compositeMove.getMoves().length * 80); - for (Move move : compositeMove.getMoves()) { - codeBuilder.append("+").append(convert(move).getCode()); - } - final String code = codeBuilder.substring(1); - return () -> code; - } else if (o instanceof ListAssignMove listAssignMove) { - return () -> convert(listAssignMove.getMovedValue()) - + " {null->" - + convert(listAssignMove.getDestinationEntity()) - + "[" + listAssignMove.getDestinationIndex() + "]}"; - } else if (o instanceof ListUnassignMove listUnassignMove) { - return () -> convert(listUnassignMove.getMovedValue()) - + " {" + convert(listUnassignMove.getSourceEntity()) - + "[" + listUnassignMove.getSourceIndex() + "]->null}"; - } else if (o instanceof ListChangeMove listChangeMove) { - return () -> convert(listChangeMove.getMovedValue()) - + " {" + convert(listChangeMove.getSourceEntity()) - + "[" + listChangeMove.getSourceIndex() + "]->" - + convert(listChangeMove.getDestinationEntity()) - + "[" + listChangeMove.getDestinationIndex() + "]}"; - } else if (o instanceof ListSwapMove listSwapMove) { - return () -> convert(listSwapMove.getLeftValue()) - + " {" + convert(listSwapMove.getLeftEntity()) - + "[" + listSwapMove.getLeftIndex() + "]} <-> " - + convert(listSwapMove.getRightValue()) - + " {" + convert(listSwapMove.getRightEntity()) - + "[" + listSwapMove.getRightIndex() + "]}"; - } else if (o instanceof SubListChangeMove subListChangeMove) { - return () -> "|" + subListChangeMove.getSubListSize() - + "| {" + convert(subListChangeMove.getSourceEntity()) - + "[" + subListChangeMove.getFromIndex() - + ".." + subListChangeMove.getToIndex() - + "]-" + (subListChangeMove.isReversing() ? "reversing->" : ">") - + convert(subListChangeMove.getDestinationEntity()) - + "[" + subListChangeMove.getDestinationIndex() + "]}"; - } else if (o instanceof SubListUnassignMove subListUnassignMove) { - return () -> "|" + subListUnassignMove.getSubListSize() - + "| {" + convert(subListUnassignMove.getSourceEntity()) - + "[" + subListUnassignMove.getFromIndex() - + ".." + subListUnassignMove.getToIndex() - + "]->null}"; - } else if (o instanceof SubListSwapMove subListSwapMove) { - return () -> "{" + convert(subListSwapMove.getLeftSubList()).getCode() - + "} <-" + (subListSwapMove.isReversing() ? "reversing-" : "") - + "> {" + convert(subListSwapMove.getRightSubList()).getCode() + "}"; - } else if (o instanceof List list) { - StringBuilder codeBuilder = new StringBuilder("["); - boolean firstElement = true; - for (Object element : list) { - if (firstElement) { - firstElement = false; - } else { - codeBuilder.append(", "); + switch (o) { + case CodeAssertable assertable -> { + return assertable; + } + case SelectorBasedNoChangeMove selectorBasedNoChangeMove -> { + return () -> "No change"; + } + case SelectorBasedChangeMove changeMove -> { + final String code = convert(changeMove.getEntity()).getCode() + + "->" + convert(changeMove.getToPlanningValue()).getCode(); + return () -> code; + } + case SelectorBasedSwapMove swapMove -> { + final String code = convert(swapMove.getLeftEntity()).getCode() + + "<->" + convert(swapMove.getRightEntity()).getCode(); + return () -> code; + } + case SelectorBasedPillarChangeMove pillarChangeMove -> { + final String code = pillarChangeMove.getPillar() + + "->" + convert(pillarChangeMove.getToPlanningValue()).getCode(); + return () -> code; + } + case SelectorBasedCompositeMove compositeMove -> { + StringBuilder codeBuilder = new StringBuilder(compositeMove.getMoves().length * 80); + for (Move move : compositeMove.getMoves()) { + codeBuilder.append("+").append(convert(move).getCode()); } - codeBuilder.append(convert(element).getCode()); - } - codeBuilder.append("]"); - final String code = codeBuilder.toString(); - return () -> code; - } else if (o instanceof SubList subList) { - return () -> convert(subList.entity()) + "[" + subList.fromIndex() + "+" + subList.length() + "]"; - } else if (o instanceof UnassignedElement unassignedLocation) { - return unassignedLocation::toString; - } else if (o instanceof PositionInList locationInList) { - return () -> convert(locationInList.entity()) + "[" + locationInList.index() + "]"; + final String code = codeBuilder.substring(1); + return () -> code; + } + case SelectorBasedListAssignMove listAssignMove -> { + return () -> convert(listAssignMove.getMovedValue()) + + " {null->" + + convert(listAssignMove.getDestinationEntity()) + + "[" + listAssignMove.getDestinationIndex() + "]}"; + } + case SelectorBasedListUnassignMove listUnassignMove -> { + return () -> convert(listUnassignMove.getMovedValue()) + + " {" + convert(listUnassignMove.getSourceEntity()) + + "[" + listUnassignMove.getSourceIndex() + "]->null}"; + } + case SelectorBasedListChangeMove listChangeMove -> { + return () -> convert(listChangeMove.getMovedValue()) + + " {" + convert(listChangeMove.getSourceEntity()) + + "[" + listChangeMove.getSourceIndex() + "]->" + + convert(listChangeMove.getDestinationEntity()) + + "[" + listChangeMove.getDestinationIndex() + "]}"; + } + case SelectorBasedListSwapMove listSwapMove -> { + return () -> convert(listSwapMove.getLeftValue()) + + " {" + convert(listSwapMove.getLeftEntity()) + + "[" + listSwapMove.getLeftIndex() + "]} <-> " + + convert(listSwapMove.getRightValue()) + + " {" + convert(listSwapMove.getRightEntity()) + + "[" + listSwapMove.getRightIndex() + "]}"; + } + case SelectorBasedSubListChangeMove subListChangeMove -> { + return () -> "|" + subListChangeMove.getSubListSize() + + "| {" + convert(subListChangeMove.getSourceEntity()) + + "[" + subListChangeMove.getFromIndex() + + ".." + subListChangeMove.getToIndex() + + "]-" + (subListChangeMove.isReversing() ? "reversing->" : ">") + + convert(subListChangeMove.getDestinationEntity()) + + "[" + subListChangeMove.getDestinationIndex() + "]}"; + } + case SelectorBasedSubListUnassignMove subListUnassignMove -> { + return () -> "|" + subListUnassignMove.getSubListSize() + + "| {" + convert(subListUnassignMove.getSourceEntity()) + + "[" + subListUnassignMove.getFromIndex() + + ".." + subListUnassignMove.getToIndex() + + "]->null}"; + } + case SelectorBasedSubListSwapMove subListSwapMove -> { + return () -> "{" + convert(subListSwapMove.getLeftSubList()).getCode() + + "} <-" + (subListSwapMove.isReversing() ? "reversing-" : "") + + "> {" + convert(subListSwapMove.getRightSubList()).getCode() + "}"; + } + case List list -> { + StringBuilder codeBuilder = new StringBuilder("["); + boolean firstElement = true; + for (Object element : list) { + if (firstElement) { + firstElement = false; + } else { + codeBuilder.append(", "); + } + codeBuilder.append(convert(element).getCode()); + } + codeBuilder.append("]"); + final String code = codeBuilder.toString(); + return () -> code; + } + case SubList subList -> { + return () -> convert(subList.entity()) + "[" + subList.fromIndex() + "+" + subList.length() + "]"; + } + case UnassignedElement unassignedLocation -> { + return unassignedLocation::toString; + } + case PositionInList locationInList -> { + return () -> convert(locationInList.entity()) + "[" + locationInList.index() + "]"; + } + default -> { + } } throw new AssertionError(("o's class (" + o.getClass() + ") cannot be converted to CodeAssertable.")); } 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 2f6f85d6122..db66680bb59 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 @@ -29,6 +29,7 @@ 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; +import ai.timefold.solver.core.impl.move.MoveDirector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.score.DummySimpleScoreEasyScoreCalculator; @@ -154,8 +155,9 @@ public static TestdataSolution generateTestdataSolution(String code, int entityA public static > InnerScoreDirector mockRebasingScoreDirector(SolutionDescriptor solutionDescriptor, Object[][] lookUpMappings) { InnerScoreDirector scoreDirector = mock(InnerScoreDirector.class); - when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); - when(scoreDirector.lookUpWorkingObject(any())).thenAnswer(invocation -> { + MoveDirector moveDirector = mock(MoveDirector.class); + when(moveDirector.getScoreDirector()).thenReturn(scoreDirector); + when(moveDirector.rebase(any())).thenAnswer(invocation -> { var externalObject = invocation.getArguments()[0]; if (externalObject == null) { return null; @@ -167,6 +169,8 @@ public static TestdataSolution generateTestdataSolution(String code, int entityA } throw new IllegalStateException("No method mocked for parameter (" + externalObject + ")."); }); + when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); + when(scoreDirector.getMoveDirector()).thenReturn(moveDirector); return scoreDirector; } diff --git a/docs/src/modules/ROOT/nav.adoc b/docs/src/modules/ROOT/nav.adoc index a9f17eeb79f..ba1384f4427 100644 --- a/docs/src/modules/ROOT/nav.adoc +++ b/docs/src/modules/ROOT/nav.adoc @@ -24,6 +24,7 @@ ** xref:optimization-algorithms/construction-heuristics.adoc[leveloffset=+1] ** xref:optimization-algorithms/local-search.adoc[leveloffset=+1] ** xref:optimization-algorithms/exhaustive-search.adoc[leveloffset=+1] +** xref:optimization-algorithms/neighborhoods.adoc[leveloffset=+1] ** xref:optimization-algorithms/move-selector-reference.adoc[leveloffset=+1] * xref:responding-to-change/responding-to-change.adoc[leveloffset=+1] * xref:integration/integration.adoc[leveloffset=+1] diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/performance.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/performance.adoc index c09064bfb55..60885e26b1d 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/performance.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/performance.adoc @@ -91,7 +91,7 @@ Instead of implementing a hard constraint, it can sometimes be built in. For example, if `Lecture` A should never be assigned to `Room` X, but it uses `ValueRangeProvider` on Solution, so the `Solver` will often try to assign it to `Room` X too (only to find out that it breaks a hard constraint). Use xref:using-timefold-solver/modeling-planning-problems.adoc#valueRangeProviderOnPlanningEntity[a ValueRangeProvider on the planning entity] -or xref:optimization-algorithms/overview.adoc#filteredSelection[filtered selection] +or xref:optimization-algorithms/move-selector-reference.adoc#filteredSelection[filtered selection] to define that Course A should only be assigned a `Room` different than X. This can give a good performance gain in some use cases, not just because the move evaluation is faster, diff --git a/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc b/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc index cb27b82b632..573fa43d4bd 100644 --- a/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc +++ b/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc @@ -239,7 +239,7 @@ and xref:optimization-algorithms/move-selector-reference.adoc#kOptListMoveSelect To customize the move selectors, add a `nearbySelection` element in the `destinationSelector`, `valueSelector` or `subListSelector` -and use xref:optimization-algorithms/overview.adoc#mimicSelection[mimic selection] +and use xref:optimization-algorithms/move-selector-reference.adoc#mimicSelection[mimic selection] to specify which destination, value, or subList should be nearby the selection. [source,xml,options="nowrap"] diff --git a/docs/src/modules/ROOT/pages/integration/config-properties.adoc b/docs/src/modules/ROOT/pages/integration/config-properties.adoc index c878c1e5905..4179d64dbf7 100644 --- a/docs/src/modules/ROOT/pages/integration/config-properties.adoc +++ b/docs/src/modules/ROOT/pages/integration/config-properties.adoc @@ -29,7 +29,7 @@ Enable xref:using-timefold-solver/running-the-solver.adoc#environmentMode[runtim implementation during development. {property_prefix}timefold.solver.{solver_name_prefix}constraint-stream-profiling-enabled:: -Enable xref:constraints-and-score/performance.adoc#constraintProfiling[constraint profiling] to identify the constraints taking the most time in score calculation and thus might be worth optimizing. +Enable xref:enterprise-edition/enterprise-edition.adoc#constraintProfiling[constraint profiling] to identify the constraints taking the most time in score calculation and thus might be worth optimizing. {property_prefix}timefold.solver.{solver_name_prefix}daemon:: Enable xref:responding-to-change/responding-to-change.adoc#daemon[daemon mode]. diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc index 5510e141612..ddc55f0ef47 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc @@ -8,4 +8,5 @@ include::overview.adoc[leveloffset=+1] include::construction-heuristics.adoc[leveloffset=+1] include::local-search.adoc[leveloffset=+1] include::exhaustive-search.adoc[leveloffset=+1] +include::neighborhoods.adoc[leveloffset=+1] include::move-selector-reference.adoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc index 469471a4a37..c876191db76 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc @@ -401,10 +401,10 @@ Advanced configuration with <> for Per step, the `QueuedEntityPlacer` selects one uninitialized entity from the `EntitySelector` and applies the winning `Move` (out of all the moves for that entity generated by the ``MoveSelector``). -The xref:optimization-algorithms/overview.adoc#mimicSelection[mimic selection] ensures that the winning `Move` changes only the selected entity. +The xref:optimization-algorithms/move-selector-reference.adoc#mimicSelection[mimic selection] ensures that the winning `Move` changes only the selected entity. To customize the entity or value sorting, -see xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection]. +see xref:optimization-algorithms/move-selector-reference.adoc#sortedSelection[sorted selection]. For scaling out, see <>. If there are multiple planning variables, there's one `ChangeMoveSelector` per planning variable, @@ -712,10 +712,10 @@ Advanced configuration with <> for a singl Per step, the `PooledEntityPlacer` applies the winning `Move` (out of all the moves for that entity generated by the ``MoveSelector``). To customize the entity or value sorting, -see xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection]. +see xref:optimization-algorithms/move-selector-reference.adoc#sortedSelection[sorted selection]. Other `Selector` customization -(such as xref:optimization-algorithms/overview.adoc#filteredSelection[filtering] -and xref:optimization-algorithms/overview.adoc#limitedSelection[limiting]) +(such as xref:optimization-algorithms/move-selector-reference.adoc#filteredSelection[filtering] +and xref:optimization-algorithms/move-selector-reference.adoc#limitedSelection[limiting]) is supported too. For scaling out, see <>. @@ -905,5 +905,5 @@ It is supported to only partition the Construction Heuristic phase. Other `Selector` customizations can also reduce the number of moves generated by step: -* xref:optimization-algorithms/overview.adoc#filteredSelection[Filtered selection] -* xref:optimization-algorithms/overview.adoc#limitedSelection[Limited selection] +* xref:optimization-algorithms/move-selector-reference.adoc#filteredSelection[Filtered selection] +* xref:optimization-algorithms/move-selector-reference.adoc#limitedSelection[Limited selection] diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc index 772ea326c5d..b60efaedc8a 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc @@ -5,49 +5,809 @@ :icons: font This chapter describes the move selectors that can be used to select moves for the optimization algorithms. -For a general introduction to move selectors, -see xref:optimization-algorithms/overview.adoc#moveAndNeighborhoodSelection[Move and neighborhood selection]. -The following `MoveSelector` implementations are available out of the box: +NOTE: Move Selectors are not public API and even though they are not yet deprecated, we encourage you to check out the xref:optimization-algorithms/neighborhoods.adoc[the Neighborhoods API], which will eventually entirely replace the Move Selectors API. -[cols="1,2a",options="header"] -|=== -|Name |Description +[#whatIsAMoveSelector] +== What is a `MoveSelector`? -|<> -|Change 1 entity's variable +A ``MoveSelector``'s main function is to create `Iterator` when needed. +An optimization algorithm will iterate through a subset of those moves. -|<> -|Swap all variables of 2 entities +Here's an example how to configure a `changeMoveSelector` for the optimization algorithm Local Search: -|<> -|Change a set of entities with the same value +[source,xml,options="nowrap"] +---- + + + ... + +---- + +Out of the box, this works and all properties of the `changeMoveSelector` are defaulted sensibly (unless that fails fast due to ambiguity). On the other hand, the configuration can be customized significantly for specific use cases. +For example: you might want to configure a <> to discard pointless moves. + + +[#subselectingOfEntitiesValuesAndOtherMoves] +=== Subselecting of entities, values, and other moves + +To create a ``Move``, a `MoveSelector` needs to select one or more planning entities and/or planning values to move. +Just like ``MoveSelector``s, ``EntitySelector``s and ``ValueSelector``s need to support a similar feature set (such as scalable just-in-time selection). Therefore, they all implement a common interface `Selector` and they are configured similarly. + +A MoveSelector is often composed out of ``EntitySelector``s, ``ValueSelector``s or even other ``MoveSelector``s, which can be configured individually if desired: + +[source,xml,options="nowrap"] +---- + + + + ... + + + ... + + ... + + + ... + + +---- + +Together, this structure forms a `Selector` tree: + +image::optimization-algorithms/overview/selectorTree.png[align="center"] + +The root of this tree is a `MoveSelector` which is injected into the optimization algorithm implementation to be (partially) iterated in every step. +For a full list of `MoveSelector` implementations available out of the box, +see xref:optimization-algorithms/move-selector-reference.adoc[Move Selector reference]. + + +[#combiningMultipleMoveSelectors] +=== Combining multiple ``MoveSelector``s + + +[#unionMoveSelector] +==== `unionMoveSelector` + +A `unionMoveSelector` selects a `Move` by selecting one of its `MoveSelector` children to supply the next ``Move``. + +Simplest configuration: + +[source,xml,options="nowrap"] +---- + + <...MoveSelector/> + <...MoveSelector/> + <...MoveSelector/> + ... + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + + ... + ... + + + ... + ... + + <...MoveSelector> + ... + ... + + ... + ...ProbabilityWeightFactory + +---- + +The `selectorProbabilityWeightFactory` determines in `selectionOrder` ``RANDOM`` how often a `MoveSelector` child is selected to supply the next Move. +By default, each `MoveSelector` child has the same chance of being selected. + +image::optimization-algorithms/overview/selectorProbabilityInUnion.png[align="center"] + +Change the `fixedProbabilityWeight` of such a child to select it more often. +For example, the `unionMoveSelector` can return a `SwapMove` twice as often as a ``ChangeMove``: + +[source,xml,options="nowrap"] +---- + + + 1.0 + ... + + + 2.0 + ... + + +---- + +The number of possible ``ChangeMove``s is very different from the number of possible ``SwapMove``s and furthermore it's problem dependent. +To give each individual `Move` the same selection chance (as opposed to each ``MoveSelector``), use the ``FairSelectorProbabilityWeightFactory``: + +[source,xml,options="nowrap"] +---- + + + + ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FairSelectorProbabilityWeightFactory + +---- + + +[#cartesianProductMoveSelector] +==== `cartesianProductMoveSelector` + +A `cartesianProductMoveSelector` selects a new ``CompositeMove``. +It builds that `CompositeMove` by selecting one `Move` per `MoveSelector` child and adding it to the ``CompositeMove``. + +Simplest configuration: + +[source,xml,options="nowrap"] +---- + + <...MoveSelector/> + <...MoveSelector/> + <...MoveSelector/> + ... + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + + ... + + + ... + + <...MoveSelector> + ... + + ... + true + +---- + +The `ignoreEmptyChildIterators` property (true by default) will ignore every empty `childMoveSelector` to avoid returning no moves. +For example: a cartesian product of `changeMoveSelector` A and B, for which B is empty (because all it's entities are pinned) returns no move if `ignoreEmptyChildIterators` is `false` and the moves of A if `ignoreEmptyChildIterators` is ``true``. + +To enforce that two child selectors use the same entity or value efficiently, use <>, not move filtering. + + +[#entitySelector] +=== `EntitySelector` + +Simplest configuration: + +[source,xml,options="nowrap"] +---- + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + org.acme.vehiclerouting.domain.Vehicle + +---- + +The `entityClass` property is only required if it cannot be deduced automatically because there are multiple entity classes. + + +[#valueSelector] +=== `ValueSelector` + +Simplest configuration: + +[source,xml,options="nowrap"] +---- + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + +---- + +The `variableName` property is only required if it cannot be deduced automatically because there are multiple variables (for the related entity class). + +In exotic Construction Heuristic configurations, the `entityClass` from the `EntitySelector` sometimes needs to be downcasted, which can be done with the property ``downcastEntityClass``: + +[source,xml,options="nowrap"] +---- + + ...LeadingExam + +---- + +If a selected entity cannot be downcasted, the `ValueSelector` is empty for that entity. + + +[#generalSelectorFeatures] +=== General `Selector` features + + +[#cacheType] +==== `CacheType`: create moves ahead of time or just in time + +A ``Selector``'s `cacheType` determines when a selection (such as a ``Move``, an entity, a value, ...) +is created and how long it lives. + +Almost every `Selector` supports setting a ``cacheType``: + +[source,xml,options="nowrap"] +---- + + PHASE + ... + +---- + +The following ``cacheType``s are supported: + +* `JUST_IN_TIME` (default, recommended): Not cached. Construct each selection (``Move``, ...) just before it's used. +This scales up well in memory footprint. +* ``STEP``: Cached. Create each selection (``Move``, ...) at the beginning of a step and cache them in a list for the remainder of the step. +This scales up badly in memory footprint. +* ``PHASE``: Cached. Create each selection (``Move``, ...) at the beginning of a solver phase and cache them in a list for the remainder of the phase. Some selections cannot be phase cached because the list changes every step. +This scales up badly in memory footprint, but has a slight performance gain. +* ``SOLVER``: Cached. Create each selection (``Move``, ...) at the beginning of a `Solver` and cache them in a list for the remainder of the ``Solver``. Some selections cannot be solver cached because the list changes every step. +This scales up badly in memory footprint, but has a slight performance gain. + +A `cacheType` can be set on composite selectors too: + +[source,xml,options="nowrap"] +---- + + PHASE + + + ... + +---- + +Nested selectors of a cached selector cannot be configured to be cached themselves, unless it's a higher ``cacheType``. +For example: a `STEP` cached `unionMoveSelector` can contain a `PHASE` cached ``changeMoveSelector``, +but it cannot contain a `STEP` cached ``changeMoveSelector``. + + +[#selectionOrder] +==== `SelectionOrder`: original, sorted, random, shuffled, or probabilistic + +A ``Selector``'s `selectionOrder` determines the order in which the selections (such as ``Move``s, entities, values, ...) are iterated. +An optimization algorithm will usually only iterate through a subset of its ``MoveSelector``'s selections, starting from the start, so the `selectionOrder` is critical to decide which ``Move``s are actually evaluated. + +Almost every `Selector` supports setting a ``selectionOrder``: + +[source,xml,options="nowrap"] +---- + + ... + RANDOM + ... + +---- + +The following ``selectionOrder``s are supported: + +* ``ORIGINAL``: Select the selections (``Move``s, entities, values, ...) in default order. Each selection will be selected only once. +** For example: A0, A1, A2, A3, ..., B0, B1, B2, B3, ..., C0, C1, C2, C3, ... +* SORTED: Select the selections (``Move``s, entities, values, ...) in sorted order. Each selection will be selected only once. Requires ``cacheType >= STEP``. Mostly used on an `entitySelector` or `valueSelector` for construction heuristics. See <>. +** For example: A0, B0, C0, ..., A2, B2, C2, ..., A1, B1, C1, ... +* RANDOM (default): Select the selections (``Move``s, entities, values, ...) in non-shuffled random order. A selection might be selected multiple times. This scales up well in performance because it does not require caching. +** For example: C2, A3, B1, C2, A0, C0, ... +* SHUFFLED: Select the selections (``Move``s, entities, values, ...) in shuffled random order. Each selection will be selected only once. Requires ``cacheType >= STEP``. This scales up badly in performance, not just because it requires caching, but also because a random number is generated for each element, even if it's not selected (which is the grand majority when scaling up). +** For example: C2, A3, B1, A0, C0, ... +* PROBABILISTIC: Select the selections (``Move``s, entities, values, ...) in random order, based on the selection probability of each element. A selection with a higher probability has a higher chance to be selected than elements with a lower probability. A selection might be selected multiple times. Requires ``cacheType >= STEP``. Mostly used on an `entitySelector` or ``valueSelector``. See <>. +** For example: B1, B1, A1, B2, B1, C2, B1, B1, ... + +A `selectionOrder` can be set on composite selectors too. + +[NOTE] +==== +When a `Selector` is cached, all of its nested ``Selector``s will naturally default to `selectionOrder` ``ORIGINAL``. +Avoid overwriting the `selectionOrder` of those nested ``Selector``s. +==== + + +[#recommendedCombinationsOfCacheTypeAndSelectionOrder] +==== Recommended combinations of `CacheType` and `SelectionOrder` + + +[#justInTimeRandomSelection] +===== Just in time random selection (default) + +This combination is great for big use cases (10 000 entities or more), as it scales up well in memory footprint and performance. +Other combinations are often not even viable on such sizes. +It works for smaller use cases too, so it's a good way to start out. +It's the default, so this explicit configuration of `cacheType` and `selectionOrder` is actually obsolete: + +[source,xml,options="nowrap"] +---- + + JUST_IN_TIME + RANDOM + + + + +---- + +Here's how it works. +When `Iterator.next()` is called, a child `MoveSelector` is randomly selected (1), which creates a random `Move` (2, 3, 4) and is then returned (5): + +image::optimization-algorithms/overview/jitRandomSelection.png[align="center"] + +Notice that *it never creates a list of ``**Move**``s* and it generates random numbers only for ``Move``s that are actually selected. + + +[#cachedShuffledSelection] +===== Cached shuffled selection + +This combination often wins for small use cases (1000 entities or less). +Beyond that size, it scales up badly in memory footprint and performance. + +[source,xml,options="nowrap"] +---- + + PHASE + SHUFFLED + + + + +---- + +Here's how it works: At the start of the phase (or step depending on the ``cacheType``), all moves are created (1) and cached (2). When `MoveSelector.iterator()` is called, the moves are shuffled (3). When `Iterator.next()` is called, the next element in the shuffled list is returned (4): + +image::optimization-algorithms/overview/cachedShuffledSelection.png[align="center"] + +Notice that **each ``Move`` will only be selected once**, even though they are selected in random order. + +Use cacheType PHASE if none of the (possibly nested) Selectors require ``STEP``. +Otherwise, do something like this: + +[source,xml,options="nowrap"] +---- + + STEP + SHUFFLED + + + PHASE + + + PHASE + + + +---- + + +[#cachedRandomSelection] +===== Cached random selection + +This combination is often a worthy competitor for medium use cases, especially with fast stepping optimization algorithms (such as Simulated Annealing). Unlike cached shuffled selection, it doesn't waste time shuffling the moves list at the beginning of every step. + +[source,xml,options="nowrap"] +---- + + PHASE + RANDOM + + + + +---- + + +[#filteredSelection] +==== Filtered selection + +There can be certain moves that you don't want to select, because: + +* The move is pointless and would only waste CPU time. +For example, swapping two lectures of the same course will result in the same score and the same schedule, +because all lectures of one course are interchangeable (same teacher, same students, same topic). +* Doing the move would break xref:constraints-and-score/performance.adoc#buildInHardConstraint[a built-in hard constraint], +so the solution would be infeasible but the score function doesn't check built-in hard constraints for performance reasons. +For example, don't change a gym lecture to a room which is not a gym room. +It's usually better to not use move filtering for such cases, +because it allows the metaheuristics to temporarily break hard constraints to escape local optima. ++ +[NOTE] +==== +Any built-in hard constraint must probably be filtered on every move type of every solver phase. +For example if it filters the change move of Local Search, it must also filter the swap move that swaps the room of a gym lecture with another lecture for which the other lecture's original room isn't a gym room. +Furthermore, it must also filter the change moves of the Construction Heuristics (which requires an advanced configuration). +==== + +If a move is unaccepted by the filter, it's not executed and the score isn't calculated. + +image::optimization-algorithms/overview/filteredSelection.png[align="center"] + +Filtering uses the interface ``SelectionFilter``: + +[source,java,options="nowrap"] +---- +public interface SelectionFilter { + + boolean accept(ScoreDirector scoreDirector, T selection); + +} +---- + +Implement the `accept` method to return `false` on a discarded `selection` (see below). +Filtered selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` +or ``ValueSelector``. +It works with any `cacheType` and ``selectionOrder``. + +[NOTE] +==== +Apply the filter on the lowest level possible. +In most cases, you'll need to know both the entity and the value involved so you'll have to apply it on the move selector. +==== + +[NOTE] +==== +`SelectionFilter` implementations are expected to be stateless. +The solver may choose to reuse them in different contexts. +==== + +[#filteredMoveSelection] +===== Filtered move selection + +Unaccepted moves will not be selected and will therefore never need to be undone: + +[source,java,options="nowrap"] +---- +public class DifferentCourseSwapMoveFilter implements SelectionFilter { + + @Override + public boolean accept(ScoreDirector scoreDirector, SwapMove move) { + Lecture leftLecture = (Lecture) move.getLeftEntity(); + Lecture rightLecture = (Lecture) move.getRightEntity(); + return !leftLecture.getCourse().equals(rightLecture.getCourse()); + } + +} +---- + +Configure the `filterClass` on every targeted `moveSelector` +(potentially both in the Local Search and the Construction Heuristics if it filters ``ChangeMove``s): + +[source,xml,options="nowrap"] +---- + + ...DifferentCourseSwapMoveFilter + +---- + + +[#filteredEntitySelection] +===== Filtered entity selection + +Unaccepted entities will not be selected and will therefore never be used to create a move. + +[source,java,options="nowrap"] +---- +public class LongLectureSelectionFilter implements SelectionFilter { + + @Override + public boolean accept(ScoreDirector scoreDirector, Lecture lecture) { + return lecture.isLong(); + } + +} +---- + +Configure the `filterClass` on every targeted `entitySelector` (potentially both in the Local Search and the Construction Heuristics): + +[source,xml,options="nowrap"] +---- + + + ...LongLectureSelectionFilter + + +---- + +If that filter should apply on all entities, configure it as a xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[global pinningFilter] instead. + +[#filteredValueSelection] +===== Filtered value selection + +Unaccepted values will not be selected and will therefore never be used to create a move. + +[source,java,options="nowrap"] +---- +public class LongPeriodSelectionFilter implements SelectionFilter { + + @Override + public boolean accept(ScoreDirector scoreDirector, Period period) { + return period(); + } + +} +---- + +Configure the `filterClass` on every targeted `valueSelector` (potentially both in the Local Search and the Construction Heuristics): + +[source,xml,options="nowrap"] +---- + + + ...LongPeriodSelectionFilter + + +---- + + +[#sortedSelection] +==== Sorted selection + +Sorted selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` or ``ValueSelector``. +It does not work with `cacheType` ``JUST_IN_TIME`` and it only works with ``selectionOrder`` ``SORTED``. + +It's mostly used in construction heuristics. + +[NOTE] +==== +If the chosen construction heuristic implies sorting, for example `FIRST_FIT_DECREASING` implies that the `EntitySelector` is sorted, there is no need to explicitly configure a `Selector` with sorting. +If you do explicitly configure the ``Selector``, it overwrites the default settings of that construction heuristic. +==== + + +[#sortedSelectionBySorterManner] +===== Sorted selection by `SorterManner` + +Some `Selector` types implement a `SorterManner` out of the box: + +* `EntitySelector` supports: +** ``DESCENDING``: Sorts the planning entities in descending order based on a give metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]). Requires that planning entity is annotated on the domain model. ++ +[source,xml,options="nowrap"] +---- + + PHASE + SORTED + DESCENDING + +---- +* `ValueSelector` supports: +** ``ASCENDING``: Sorts the planning values in ascending order based on a given metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]). Requires that planning value is annotated on the domain model. ++ +[source,xml,options="nowrap"] +---- + + PHASE + SORTED + ASCENDING + +---- + + +[#sortedSelectionByComparator] +===== Sorted selection by `Comparator` + +An easy way to sort a `Selector` is with a plain old ``Comparator``: + +[source,java,options="nowrap"] +---- +public class VisitComparator implements Comparator { + + public int compare(Visit a, Visit b) { + return new CompareToBuilder() + .append(a.getServiceDuration(), b.getServiceDuration()) + .append(a.getId(), b.getId()) + .toComparison(); + } + +} +---- + +You'll also need to configure it (unless it's annotated on the domain model and automatically applied by the optimization algorithm): + +[source,xml,options="nowrap"] +---- + + PHASE + SORTED + ...VisitComparator + DESCENDING + +---- + +[NOTE] +==== +`Comparator` implementations are expected to be stateless. +The solver may choose to reuse them in different contexts. +==== + + +[#sortedSelectionByComparatorFactory] +===== [[sortedSelectionBySelectionSorterWeightFactory]]Sorted selection by `ComparatorFactory` + +If you need the entire solution to sort a ``Selector``, use a `ComparatorFactory` instead: + +[source,java,options="nowrap"] +---- +public interface ComparatorFactory { + + Comparator createComparator(Solution_ solution); + +} +---- + +You'll also need to configure it (unless it's annotated on the domain model and automatically applied by the optimization algorithm): + +[source,xml,options="nowrap"] +---- + + PHASE + SORTED + ...MyComparatorFactory + DESCENDING + +---- + +[NOTE] +==== +`ComparatorFactory` implementations are expected to be stateless. +The solver may choose to reuse them in different contexts. +==== + + +[#sortedSelectionBySelectionSorter] +===== Sorted selection by `SelectionSorter` + +Alternatively, you can also use the interface `SelectionSorter` directly: + +[source,java,options="nowrap"] +---- +public interface SelectionSorter { + + void sort(ScoreDirector scoreDirector, List selectionList); + +} +---- + + + +[source,xml,options="nowrap"] +---- + + PHASE + SORTED + ...MyEntitySorter + +---- + +[NOTE] +==== +`SelectionSorter` implementations are expected to be stateless. +The solver may choose to reuse them in different contexts. +==== + + +[#probabilisticSelection] +==== Probabilistic selection -|<> -|Swap 2 sets of entities with the same values +Probabilistic selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` or ``ValueSelector``. +It does not work with `cacheType` ``JUST_IN_TIME`` and it only works with ``selectionOrder`` ``PROBABILISTIC``. -|<> -|Take a subset of entities, uninitialize them and run a construction heuristic to put them back +image::optimization-algorithms/overview/probabilisticSelection.png[align="center"] -|<> -|Move a list element to a different index or to another entity's list variable +Each selection has a ``probabilityWeight``, which determines the chance that selection will be selected: -|<> -|Swap 2 list elements +[source,java,options="nowrap"] +---- +public interface SelectionProbabilityWeightFactory { + + double createProbabilityWeight(ScoreDirector scoreDirector, T selection); + +} +---- + +[source,xml,options="nowrap"] +---- + + PHASE + PROBABILISTIC + ...MyEntityProbabilityWeightFactoryClass + +---- + +Assume the following entities: lesson A (probabilityWeight 2.0), lesson B (probabilityWeight 0.5) and lesson C (probabilityWeight 0.5). +Then lesson A will be selected four times more than B and C. + +[NOTE] +==== +`SelectionProbabilityWeightFactory` implementations are expected to be stateless. +The solver may choose to reuse them in different contexts. +==== + + +[#limitedSelection] +==== Limited selection + +Selecting all possible moves sometimes does not scale well enough, +especially for xref:optimization-algorithms/construction-heuristics.adoc#constructionHeuristics[construction heuristics], +which don't support xref:optimization-algorithms/local-search.adoc#acceptedCountLimit[`acceptedCountLimit`]. + +To limit the number of selected selection per step, apply a `selectedCountLimit` on the selector: + +[source,xml,options="nowrap"] +---- + + 100 + +---- + +[NOTE] +==== +To scale xref:optimization-algorithms/local-search.adoc#localSearch[Local Search], +setting xref:optimization-algorithms/local-search.adoc#acceptedCountLimit[`acceptedCountLimit`] +is usually better than using ``selectedCountLimit``. +==== + + +[#mimicSelection] +==== Mimic selection (record/replay) + +During mimic selection, one normal selector records its selection and one or multiple other special selectors replay that selection. +The recording selector acts as a normal selector and supports all other configuration properties. +A replaying selector mimics the recording selection and supports no other configuration properties. -|<> -|Move a subList from one position to another +The recording selector needs an ``id``. +A replaying selector must reference a recorder's id with a ``mimicSelectorRef``: -|<> -|Swap 2 subLists +[source,xml,options="nowrap"] +---- + + + + + + + + + + +---- + +Mimic selection is useful to create <> from two moves that affect the same entity. -|<> -|Select an entity, remove k edges from its list variable, add k new edges from the removed endpoints -|<> -|Take a subset of values, remove them from their lists, and run a construction heuristic to recreate the lists +[#nearbySelectionTeaser] +==== Nearby selection + +[NOTE] +==== +Nearby selection is a commercial feature of xref:enterprise-edition/enterprise-edition.adoc[Timefold Solver Enterprise Edition]. +==== -|=== +Read about nearby selection in the xref:enterprise-edition/enterprise-edition.adoc#nearbySelection[Nearby selection section] +of the xref:enterprise-edition/enterprise-edition.adoc[Enterprise Edition manual]. [#basicMoveSelectors] == Move selectors for basic variables @@ -71,7 +831,7 @@ Simplest configuration: If there are multiple entity classes or multiple planning variables for one entity class, a simple configuration will automatically unfold into -an xref:optimization-algorithms/overview.adoc#unionMoveSelector[union] +an <> of `ChangeMove` selectors for every planning variable. Advanced configuration: @@ -120,8 +880,7 @@ Simplest configuration: ---- If there are multiple entity classes, a simple configuration will automatically unfold -into an xref:optimization-algorithms/overview.adoc#unionMoveSelector[union] -of `SwapMove` selectors for every entity class. +into an <> of `SwapMove` selectors for every entity class. Advanced configuration: @@ -196,7 +955,7 @@ Advanced configuration: For a description of `subPillarType` and related properties, please refer to <>. The other properties are explained in <>. -This move selector doesn’t support xref:optimization-algorithms/overview.adoc#cacheType[phase or solver caching] +This move selector doesn’t support <> and step caching scales badly memory wise. @@ -248,7 +1007,7 @@ For a description of `subPillarType` and related properties, please refer to <> and <>. -This move selector doesn’t support xref:optimization-algorithms/overview.adoc#cacheType[phase or solver caching] +This move selector doesn’t support <> and step caching scales badly memory wise. [#subPillars] @@ -269,7 +1028,7 @@ If sub-pillars are enabled, the pillar itself is also included and the propertie ==== The number of sub-pillars of a pillar is exponential to the size of the pillar. For example a pillar of size 32 has `(2^32 - 1)` sub-pillars. -Therefore a `pillarSelector` only supports xref:optimization-algorithms/overview.adoc#justInTimeRandomSelection[JIT random selection] (which is the default). +Therefore a `pillarSelector` only supports <> (which is the default). ==== [#sequentialSubPillars] @@ -391,7 +1150,7 @@ Since the `RuinRecreateMove` is a coarse-grained move, it is expensive and can slow the solver down significantly. However, the default local search configuration will attempt to run it at the same frequency as the other fine-grained moves. -For that reason, we recommend that you use xref:optimization-algorithms/overview.adoc#probabilisticSelection[probabilistic selection] +For that reason, we recommend that you use <> to control the frequency of this move: [source,xml,options="nowrap"] @@ -670,7 +1429,7 @@ Since the `ListRuinRecreateMove` is a coarse-grained move, it is expensive and can slow the solver down significantly. However, the default local search configuration will attempt to run it at the same frequency as the other fine-grained moves. -For that reason, we recommend that you use xref:optimization-algorithms/overview.adoc#probabilisticSelection[probabilistic selection] +For that reason, we recommend that you use <> to control the frequency of this move: [source,xml,options="nowrap"] @@ -739,4 +1498,243 @@ public class MyStageProvider implements ListVariableStageProvider> as desired. + +A custom `Move` can be tailored to work to the advantage of your constraints. +For example, in examination scheduling, changing the period of an exam A +would also change the period of all the other exams that need to coincide with exam A. + +A custom `Move` is far more work to implement and much harder to avoid bugs than a generic ``Move``. +After implementing a custom ``Move``, turn on `environmentMode` ``TRACED_FULL_ASSERT`` to check for score corruptions. + +For information on the `Move` interface, check out the +xref:optimization-algorithms/neighborhoods.adoc#neighborhoodsMove[Move Anatomy] section of the Neighborhoods chapter. +Even though Neighborhoods is an entirely different mechanism than move selectors, +they share the `Move` interface and therefore the same custom `Move` can be used in both mechanisms. + + +[#generatingCustomMoves] +=== Generating custom moves + +Now, let's generate instances of this custom ``Move`` class. +There are 2 ways: + +[#moveListFactory] +==== `MoveListFactory`: the easy way to generate custom moves + +The easiest way to generate custom moves is by implementing the interface ``MoveListFactory``: + +[source,java,options="nowrap"] +---- +public interface MoveListFactory { + + List createMoveList(Solution_ solution); + +} +---- + +Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): + +[source,xml,options="nowrap"] +---- + + ...MyMoveFactory + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + ...MyMoveFactory + + ... + + +---- + +Because the `MoveListFactory` generates all moves at once in a ``List``, +it does not support `cacheType` ``JUST_IN_TIME``. +Therefore, `moveListFactory` uses `cacheType` ``STEP`` by default and it scales badly. + +To configure values of a `MoveListFactory` dynamically in the solver configuration +(so the xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), +add the `moveListFactoryCustomProperties` element and use xref:using-timefold-solver/configuration.adoc#customPropertiesConfiguration[custom properties]. + +[WARNING] +==== +A custom `MoveListFactory` implementation must ensure that it does not move xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[pinned entities]. +==== + + +[#moveIteratorFactory] +==== ``MoveIteratorFactory``: generate Custom moves just in time + +Use this advanced form to generate custom moves Just In Time +by implementing the `MoveIteratorFactory` interface: + +[source,java,options="nowrap"] +---- +public interface MoveIteratorFactory { + + long getSize(ScoreDirector scoreDirector); + + Iterator createOriginalMoveIterator(ScoreDirector scoreDirector); + + Iterator createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom); + +} +---- + +The `getSize()` method must return an estimation of the size. +It doesn't need to be correct, but it's better too big than too small. +The `createOriginalMoveIterator` method is called if the `selectionOrder` is `ORIGINAL` or if it is cached. +The `createRandomMoveIterator` method is called for `selectionOrder` ``RANDOM`` combined with cacheType ``JUST_IN_TIME``. + +[IMPORTANT] +==== +Don't create a collection (array, list, set or map) of ``Move``s when creating the ``Iterator``: +the whole purpose of `MoveIteratorFactory` over `MoveListFactory` is to create a `Move` just in time +in a custom ``Iterator.next()``. +==== + +For example: + +[source,java,options="nowrap"] +---- +public class PossibleAssignmentsOnlyMoveIteratorFactory implements MoveIteratorFactory { + @Override + public long getSize(ScoreDirector scoreDirector) { + // In this case, we return the exact size, but an estimate can be used + // if it too expensive to calculate or unknown + long totalSize = 0L; + var solution = scoreDirector.getWorkingSolution(); + for (MyEntity entity : solution.getEntities()) { + for (MyPlanningValue value : solution.getValues()) { + if (entity.canBeAssigned(value)) { + totalSize++; + } + } + } + return totalSize; + } + + @Override + public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { + // Only needed if selectionOrder is ORIGINAL or if it is cached + var solution = scoreDirector.getWorkingSolution(); + var entities = solution.getEntities(); + var values = solution.getValues(); + // Assumes each entity has at least one assignable value + var firstEntityIndex = 0; + var firstValueIndex = 0; + while (!entities.get(firstEntityIndex).canBeAssigned(values.get(firstValueIndex))) { + firstValueIndex++; + } + + + return new Iterator<>() { + int nextEntityIndex = firstEntityIndex; + int nextValueIndex = firstValueIndex; + + @Override + public boolean hasNext() { + return nextEntityIndex < entities.size(); + } + + @Override + public MyChangeMove next() { + var selectedEntity = entities.get(nextEntityIndex); + var selectedValue = values.get(nextValueIndex); + nextValueIndex++; + while (nextValueIndex < values.size() && !selectedEntity.canBeAssigned(values.get(nextValueIndex))) { + nextValueIndex++; + } + if (nextValueIndex >= values.size()) { + // value list exhausted, go to next entity + nextEntityIndex++; + if (nextEntityIndex < entities.size()) { + nextValueIndex = 0; + while (nextValueIndex < values.size() && !entities.get(nextEntityIndex).canBeAssigned(values.get(nextValueIndex))) { + // Assumes each entity has at least one assignable value + nextValueIndex++; + } + } + } + return new MyChangeMove(selectedEntity, selectedValue); + } + }; + } + + @Override + public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, + Random workingRandom) { + // Not needed if selectionOrder is ORIGINAL or if it is cached + var solution = scoreDirector.getWorkingSolution(); + var entities = solution.getEntities(); + var values = solution.getValues(); + + return new Iterator<>() { + @Override + public boolean hasNext() { + return !entities.isEmpty(); + } + + @Override + public MyChangeMove next() { + var selectedEntity = entities.get(workingRandom.nextInt(entities.size())); + var selectedValue = values.get(workingRandom.nextInt(values.size())); + while (!selectedEntity.canBeAssigned(selectedValue)) { + // This assumes there at least one value that can be assigned to the selected entity + selectedValue = values.get(workingRandom.nextInt(values.size())); + } + return new MyChangeMove(selectedEntity, selectedValue); + } + }; + } +} +---- + +[NOTE] +==== +The same effect can also be accomplished using <>. +==== + +Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): + +[source,xml,options="nowrap"] +---- + + ... + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + ... + + ... + + +---- + +To configure values of a `MoveIteratorFactory` dynamically in the solver configuration +(so the xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), +add the `moveIteratorFactoryCustomProperties` element and use xref:using-timefold-solver/configuration.adoc#customPropertiesConfiguration[custom properties]. + +[WARNING] +==== +A custom `MoveIteratorFactory` implementation must ensure that it does not move xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[pinned entities]. +==== diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc index 3513fc5d184..d7b774f4e55 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc @@ -4,9 +4,9 @@ :sectnums: :icons: font -IMPORTANT: The Neighborhoods API is an active research project. +IMPORTANT: The Neighborhoods API is a preview feature. It intends to simplify the creation of custom moves, eventually replacing xref:optimization-algorithms/move-selector-reference.adoc[move selectors]. -The component is under development and many key features are yet to be delivered. +The component is under development and key features are yet to be delivered. While we believe that the basic building blocks of the API are already stable, we reserve the right to change the API or remove any part of it. Your feedback is highly appreciated and will be imperative in shaping the future of this component. @@ -79,6 +79,11 @@ The `Move` interface specifies the following methods that must be implemented: This method makes changes to the given solution. `MutableSolutionView` provides methods to safely modify the planning variables of the working solution. +NOTE: `MutableSolutionView` intends to be a rich API that provides all the necessary methods +to modify the solution in a safe and efficient way. +If you are missing an operation that you need to implement your move, +please reach out to us so we can consider adding it to the API. + For compatibility with xref:optimization-algorithms/local-search.adoc#tabuSearch[Tabu Search], it is also recommended to override the following methods: diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 83718557fed..307fcbc2a56 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -162,7 +162,7 @@ This combination is very efficient, because: * A score calculation engine is *great for calculating the score* of a solution of a planning problem. It makes it easy and scalable to add additional soft or hard constraints. -It does xref:constraints-and-score/performance.adoc#incrementalScoreCalculation[incremental score calculation (deltas)] without any extra code. +It does xref:constraints-and-score/score-calculation.adoc#incrementalScoreCalculation[incremental score calculation (deltas)] without any extra code. However it tends to be not suitable to actually find new solutions. * An optimization algorithm is *great at finding new improving solutions* for a planning problem, without necessarily brute-forcing every possibility. @@ -922,12 +922,8 @@ add the `customProperties` element and use xref:using-timefold-solver/configurat :icons: font -[#moveAndNeighborhoodSelectionIntroduction] -=== Move and neighborhood introduction - - [#whatIsAMove] -==== What is a `Move`? +=== What is a `Move`? A `Move` is a change (or set of changes) from a solution A to a solution B. For example, the move below changes queen `C` from row `0` to row ``2``: @@ -951,1275 +947,43 @@ For example we can reach a very different solution in three ``changeMove``s: image::optimization-algorithms/overview/sequentialMovesNQueens04.png[align="center"] -[NOTE] -==== -There are many other types of moves besides ``changeMove``s. +NOTE: There are many other types of moves besides ``changeMove``s. Many move types are included out-of-the-box, but you can also implement custom moves. - -A `Move` can affect multiple entities or even create/delete entities. -But it must not change the problem facts. -==== +A `Move` can affect multiple entities, but it must not change the problem facts, +add or remove entities. All optimization algorithms use ``Move``s to transition from one solution to a neighbor solution. Therefore, all the optimization algorithms are confronted with `Move` selection: the craft of creating and iterating moves efficiently and the art of finding the most promising subset of random moves to evaluate first. -[#whatIsAMoveSelector] -==== What is a `MoveSelector`? - -A ``MoveSelector``'s main function is to create `Iterator` when needed. -An optimization algorithm will iterate through a subset of those moves. - -Here's an example how to configure a `changeMoveSelector` for the optimization algorithm Local Search: - -[source,xml,options="nowrap"] ----- - - - ... - ----- - -Out of the box, this works and all properties of the `changeMoveSelector` are defaulted sensibly (unless that fails fast due to ambiguity). On the other hand, the configuration can be customized significantly for specific use cases. -For example: you might want to configure a <> to discard pointless moves. - - -[#subselectingOfEntitiesValuesAndOtherMoves] -==== Subselecting of entities, values, and other moves - -To create a ``Move``, a `MoveSelector` needs to select one or more planning entities and/or planning values to move. -Just like ``MoveSelector``s, ``EntitySelector``s and ``ValueSelector``s need to support a similar feature set (such as scalable just-in-time selection). Therefore, they all implement a common interface `Selector` and they are configured similarly. - -A MoveSelector is often composed out of ``EntitySelector``s, ``ValueSelector``s or even other ``MoveSelector``s, which can be configured individually if desired: - -[source,xml,options="nowrap"] ----- - - - - ... - - - ... - - ... - - - ... - - ----- - -Together, this structure forms a `Selector` tree: - -image::optimization-algorithms/overview/selectorTree.png[align="center"] - -The root of this tree is a `MoveSelector` which is injected into the optimization algorithm implementation to be (partially) iterated in every step. -For a full list of `MoveSelector` implementations available out of the box, -see xref:optimization-algorithms/move-selector-reference.adoc[Move Selector reference]. - - -[#combiningMultipleMoveSelectors] -=== Combining multiple ``MoveSelector``s - - -[#unionMoveSelector] -==== `unionMoveSelector` - -A `unionMoveSelector` selects a `Move` by selecting one of its `MoveSelector` children to supply the next ``Move``. - -Simplest configuration: - -[source,xml,options="nowrap"] ----- - - <...MoveSelector/> - <...MoveSelector/> - <...MoveSelector/> - ... - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - - ... - ... - - - ... - ... - - <...MoveSelector> - ... - ... - - ... - ...ProbabilityWeightFactory - ----- - -The `selectorProbabilityWeightFactory` determines in `selectionOrder` ``RANDOM`` how often a `MoveSelector` child is selected to supply the next Move. -By default, each `MoveSelector` child has the same chance of being selected. - -image::optimization-algorithms/overview/selectorProbabilityInUnion.png[align="center"] - -Change the `fixedProbabilityWeight` of such a child to select it more often. -For example, the `unionMoveSelector` can return a `SwapMove` twice as often as a ``ChangeMove``: - -[source,xml,options="nowrap"] ----- - - - 1.0 - ... - - - 2.0 - ... - - ----- - -The number of possible ``ChangeMove``s is very different from the number of possible ``SwapMove``s and furthermore it's problem dependent. -To give each individual `Move` the same selection chance (as opposed to each ``MoveSelector``), use the ``FairSelectorProbabilityWeightFactory``: - -[source,xml,options="nowrap"] ----- - - - - ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FairSelectorProbabilityWeightFactory - ----- - - -[#cartesianProductMoveSelector] -==== `cartesianProductMoveSelector` - -A `cartesianProductMoveSelector` selects a new ``CompositeMove``. -It builds that `CompositeMove` by selecting one `Move` per `MoveSelector` child and adding it to the ``CompositeMove``. - -Simplest configuration: - -[source,xml,options="nowrap"] ----- - - <...MoveSelector/> - <...MoveSelector/> - <...MoveSelector/> - ... - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - - ... - - - ... - - <...MoveSelector> - ... - - ... - true - ----- - -The `ignoreEmptyChildIterators` property (true by default) will ignore every empty `childMoveSelector` to avoid returning no moves. -For example: a cartesian product of `changeMoveSelector` A and B, for which B is empty (because all it's entities are pinned) returns no move if `ignoreEmptyChildIterators` is `false` and the moves of A if `ignoreEmptyChildIterators` is ``true``. - -To enforce that two child selectors use the same entity or value efficiently, use <>, not move filtering. - - -[#entitySelector] -=== `EntitySelector` - -Simplest configuration: - -[source,xml,options="nowrap"] ----- - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - org.acme.vehiclerouting.domain.Vehicle - ----- - -The `entityClass` property is only required if it cannot be deduced automatically because there are multiple entity classes. - - -[#valueSelector] -=== `ValueSelector` - -Simplest configuration: - -[source,xml,options="nowrap"] ----- - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - ----- - -The `variableName` property is only required if it cannot be deduced automatically because there are multiple variables (for the related entity class). - -In exotic Construction Heuristic configurations, the `entityClass` from the `EntitySelector` sometimes needs to be downcasted, which can be done with the property ``downcastEntityClass``: - -[source,xml,options="nowrap"] ----- - - ...LeadingExam - ----- - -If a selected entity cannot be downcasted, the `ValueSelector` is empty for that entity. - - -[#generalSelectorFeatures] -=== General `Selector` features - - -[#cacheType] -==== `CacheType`: create moves ahead of time or just in time - -A ``Selector``'s `cacheType` determines when a selection (such as a ``Move``, an entity, a value, ...) -is created and how long it lives. - -Almost every `Selector` supports setting a ``cacheType``: - -[source,xml,options="nowrap"] ----- - - PHASE - ... - ----- - -The following ``cacheType``s are supported: - -* `JUST_IN_TIME` (default, recommended): Not cached. Construct each selection (``Move``, ...) just before it's used. -This scales up well in memory footprint. -* ``STEP``: Cached. Create each selection (``Move``, ...) at the beginning of a step and cache them in a list for the remainder of the step. -This scales up badly in memory footprint. -* ``PHASE``: Cached. Create each selection (``Move``, ...) at the beginning of a solver phase and cache them in a list for the remainder of the phase. Some selections cannot be phase cached because the list changes every step. -This scales up badly in memory footprint, but has a slight performance gain. -* ``SOLVER``: Cached. Create each selection (``Move``, ...) at the beginning of a `Solver` and cache them in a list for the remainder of the ``Solver``. Some selections cannot be solver cached because the list changes every step. -This scales up badly in memory footprint, but has a slight performance gain. - -A `cacheType` can be set on composite selectors too: - -[source,xml,options="nowrap"] ----- - - PHASE - - - ... - ----- - -Nested selectors of a cached selector cannot be configured to be cached themselves, unless it's a higher ``cacheType``. -For example: a `STEP` cached `unionMoveSelector` can contain a `PHASE` cached ``changeMoveSelector``, -but it cannot contain a `STEP` cached ``changeMoveSelector``. - - -[#selectionOrder] -==== `SelectionOrder`: original, sorted, random, shuffled, or probabilistic - -A ``Selector``'s `selectionOrder` determines the order in which the selections (such as ``Move``s, entities, values, ...) are iterated. -An optimization algorithm will usually only iterate through a subset of its ``MoveSelector``'s selections, starting from the start, so the `selectionOrder` is critical to decide which ``Move``s are actually evaluated. - -Almost every `Selector` supports setting a ``selectionOrder``: - -[source,xml,options="nowrap"] ----- - - ... - RANDOM - ... - ----- - -The following ``selectionOrder``s are supported: - -* ``ORIGINAL``: Select the selections (``Move``s, entities, values, ...) in default order. Each selection will be selected only once. -** For example: A0, A1, A2, A3, ..., B0, B1, B2, B3, ..., C0, C1, C2, C3, ... -* SORTED: Select the selections (``Move``s, entities, values, ...) in sorted order. Each selection will be selected only once. Requires ``cacheType >= STEP``. Mostly used on an `entitySelector` or `valueSelector` for construction heuristics. See <>. -** For example: A0, B0, C0, ..., A2, B2, C2, ..., A1, B1, C1, ... -* RANDOM (default): Select the selections (``Move``s, entities, values, ...) in non-shuffled random order. A selection might be selected multiple times. This scales up well in performance because it does not require caching. -** For example: C2, A3, B1, C2, A0, C0, ... -* SHUFFLED: Select the selections (``Move``s, entities, values, ...) in shuffled random order. Each selection will be selected only once. Requires ``cacheType >= STEP``. This scales up badly in performance, not just because it requires caching, but also because a random number is generated for each element, even if it's not selected (which is the grand majority when scaling up). -** For example: C2, A3, B1, A0, C0, ... -* PROBABILISTIC: Select the selections (``Move``s, entities, values, ...) in random order, based on the selection probability of each element. A selection with a higher probability has a higher chance to be selected than elements with a lower probability. A selection might be selected multiple times. Requires ``cacheType >= STEP``. Mostly used on an `entitySelector` or ``valueSelector``. See <>. -** For example: B1, B1, A1, B2, B1, C2, B1, B1, ... - -A `selectionOrder` can be set on composite selectors too. - -[NOTE] -==== -When a `Selector` is cached, all of its nested ``Selector``s will naturally default to `selectionOrder` ``ORIGINAL``. -Avoid overwriting the `selectionOrder` of those nested ``Selector``s. -==== - - -[#recommendedCombinationsOfCacheTypeAndSelectionOrder] -==== Recommended combinations of `CacheType` and `SelectionOrder` - - -[#justInTimeRandomSelection] -===== Just in time random selection (default) - -This combination is great for big use cases (10 000 entities or more), as it scales up well in memory footprint and performance. -Other combinations are often not even viable on such sizes. -It works for smaller use cases too, so it's a good way to start out. -It's the default, so this explicit configuration of `cacheType` and `selectionOrder` is actually obsolete: - -[source,xml,options="nowrap"] ----- - - JUST_IN_TIME - RANDOM - - - - ----- - -Here's how it works. -When `Iterator.next()` is called, a child `MoveSelector` is randomly selected (1), which creates a random `Move` (2, 3, 4) and is then returned (5): - -image::optimization-algorithms/overview/jitRandomSelection.png[align="center"] - -Notice that *it never creates a list of ``**Move**``s* and it generates random numbers only for ``Move``s that are actually selected. - - -[#cachedShuffledSelection] -===== Cached shuffled selection - -This combination often wins for small use cases (1000 entities or less). -Beyond that size, it scales up badly in memory footprint and performance. - -[source,xml,options="nowrap"] ----- - - PHASE - SHUFFLED - - - - ----- - -Here's how it works: At the start of the phase (or step depending on the ``cacheType``), all moves are created (1) and cached (2). When `MoveSelector.iterator()` is called, the moves are shuffled (3). When `Iterator.next()` is called, the next element in the shuffled list is returned (4): - -image::optimization-algorithms/overview/cachedShuffledSelection.png[align="center"] - -Notice that **each ``Move`` will only be selected once**, even though they are selected in random order. - -Use cacheType PHASE if none of the (possibly nested) Selectors require ``STEP``. -Otherwise, do something like this: - -[source,xml,options="nowrap"] ----- - - STEP - SHUFFLED - - - PHASE - - - PHASE - - - ----- - - -[#cachedRandomSelection] -===== Cached random selection - -This combination is often a worthy competitor for medium use cases, especially with fast stepping optimization algorithms (such as Simulated Annealing). Unlike cached shuffled selection, it doesn't waste time shuffling the moves list at the beginning of every step. - -[source,xml,options="nowrap"] ----- - - PHASE - RANDOM - - - - ----- - - -[#filteredSelection] -==== Filtered selection - -There can be certain moves that you don't want to select, because: - -* The move is pointless and would only waste CPU time. -For example, swapping two lectures of the same course will result in the same score and the same schedule, -because all lectures of one course are interchangeable (same teacher, same students, same topic). -* Doing the move would break xref:constraints-and-score/performance.adoc#buildInHardConstraint[a built-in hard constraint], -so the solution would be infeasible but the score function doesn't check built-in hard constraints for performance reasons. -For example, don't change a gym lecture to a room which is not a gym room. -It's usually better to not use move filtering for such cases, -because it allows the metaheuristics to temporarily break hard constraints to escape local optima. -+ -[NOTE] -==== -Any built-in hard constraint must probably be filtered on every move type of every solver phase. -For example if it filters the change move of Local Search, it must also filter the swap move that swaps the room of a gym lecture with another lecture for which the other lecture's original room isn't a gym room. -Furthermore, it must also filter the change moves of the Construction Heuristics (which requires an advanced configuration). -==== - -If a move is unaccepted by the filter, it's not executed and the score isn't calculated. - -image::optimization-algorithms/overview/filteredSelection.png[align="center"] - -Filtering uses the interface ``SelectionFilter``: - -[source,java,options="nowrap"] ----- -public interface SelectionFilter { - - boolean accept(ScoreDirector scoreDirector, T selection); - -} ----- - -Implement the `accept` method to return `false` on a discarded `selection` (see below). -Filtered selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` -or ``ValueSelector``. -It works with any `cacheType` and ``selectionOrder``. - -[NOTE] -==== -Apply the filter on the lowest level possible. -In most cases, you'll need to know both the entity and the value involved so you'll have to apply it on the move selector. -==== - -[NOTE] -==== -`SelectionFilter` implementations are expected to be stateless. -The solver may choose to reuse them in different contexts. -==== - -[#filteredMoveSelection] -===== Filtered move selection - -Unaccepted moves will not be selected and will therefore never have their `doMove()` method called: - -[source,java,options="nowrap"] ----- -public class DifferentCourseSwapMoveFilter implements SelectionFilter { - - @Override - public boolean accept(ScoreDirector scoreDirector, SwapMove move) { - Lecture leftLecture = (Lecture) move.getLeftEntity(); - Lecture rightLecture = (Lecture) move.getRightEntity(); - return !leftLecture.getCourse().equals(rightLecture.getCourse()); - } - -} ----- - -Configure the `filterClass` on every targeted `moveSelector` -(potentially both in the Local Search and the Construction Heuristics if it filters ``ChangeMove``s): - -[source,xml,options="nowrap"] ----- - - ...DifferentCourseSwapMoveFilter - ----- - - -[#filteredEntitySelection] -===== Filtered entity selection - -Unaccepted entities will not be selected and will therefore never be used to create a move. - -[source,java,options="nowrap"] ----- -public class LongLectureSelectionFilter implements SelectionFilter { - - @Override - public boolean accept(ScoreDirector scoreDirector, Lecture lecture) { - return lecture.isLong(); - } - -} ----- - -Configure the `filterClass` on every targeted `entitySelector` (potentially both in the Local Search and the Construction Heuristics): - -[source,xml,options="nowrap"] ----- - - - ...LongLectureSelectionFilter - - ----- - -If that filter should apply on all entities, configure it as a xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[global pinningFilter] instead. - -[#filteredValueSelection] -===== Filtered value selection - -Unaccepted values will not be selected and will therefore never be used to create a move. - -[source,java,options="nowrap"] ----- -public class LongPeriodSelectionFilter implements SelectionFilter { - - @Override - public boolean accept(ScoreDirector scoreDirector, Period period) { - return period(); - } - -} ----- - -Configure the `filterClass` on every targeted `valueSelector` (potentially both in the Local Search and the Construction Heuristics): - -[source,xml,options="nowrap"] ----- - - - ...LongPeriodSelectionFilter - - ----- - - -[#sortedSelection] -==== Sorted selection +[#customMoves] +=== Custom moves -Sorted selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` or ``ValueSelector``. -It does not work with `cacheType` ``JUST_IN_TIME`` and it only works with ``selectionOrder`` ``SORTED``. +[#whichMoveTypesMightBeMissing] +==== Which move types might be missing in my implementation? -It's mostly used in construction heuristics. +To determine which move types might be missing in your implementation, +run a xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] __for a short amount of time__ +and xref:using-timefold-solver/benchmarking-and-tweaking.adoc#writeTheOutputSolutionOfBenchmarkRuns[configure it to write the best solutions to disk]. +Take a look at such a best solution: it will likely be a local optima. +Try to figure out if there's a move that could get out of that local optima faster. -[NOTE] -==== -If the chosen construction heuristic implies sorting, for example `FIRST_FIT_DECREASING` implies that the `EntitySelector` is sorted, there is no need to explicitly configure a `Selector` with sorting. -If you do explicitly configure the ``Selector``, it overwrites the default settings of that construction heuristic. -==== +If you find one, implement that coarse-grained move, mix it with the existing moves +and benchmark it against the previous configurations to see if you want to keep it. +[#customMovesHowToImplement] +==== How to implement custom moves -[#sortedSelectionBySorterManner] -===== Sorted selection by `SorterManner` +At the moment, Timefold Solver offers two ways to implement custom moves. -Some `Selector` types implement a `SorterManner` out of the box: +Neighborhoods API:: The xref:optimization-algorithms/neighborhoods.adoc[Neighborhoods API] is a simple and intuitive way to implement custom moves. +It is currently a preview feature and is expected to be the main way to implement custom moves in the near future. +However, it is possible that some advanced features are not yet supported. +We encourage you to try it out and give feedback on the features you need. +Move Selector API:: The xref:optimization-algorithms/move-selector-reference.adoc[Move Selector API] is the original way of implementing custom moves. +It is complex and very difficult to implement correctly and test, +but it supports all possible features. +It is expected to be deprecated in the near future, but it is still available for use. -* `EntitySelector` supports: -** ``DESCENDING``: Sorts the planning entities in descending order based on a give metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]). Requires that planning entity is annotated on the domain model. -+ -[source,xml,options="nowrap"] ----- - - PHASE - SORTED - DESCENDING - ----- -* `ValueSelector` supports: -** ``ASCENDING``: Sorts the planning values in ascending order based on a given metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]). Requires that planning value is annotated on the domain model. -+ -[source,xml,options="nowrap"] ----- - - PHASE - SORTED - ASCENDING - ----- - - -[#sortedSelectionByComparator] -===== Sorted selection by `Comparator` - -An easy way to sort a `Selector` is with a plain old ``Comparator``: - -[source,java,options="nowrap"] ----- -public class VisitComparator implements Comparator { - - public int compare(Visit a, Visit b) { - return new CompareToBuilder() - .append(a.getServiceDuration(), b.getServiceDuration()) - .append(a.getId(), b.getId()) - .toComparison(); - } - -} ----- - -You'll also need to configure it (unless it's annotated on the domain model and automatically applied by the optimization algorithm): - -[source,xml,options="nowrap"] ----- - - PHASE - SORTED - ...VisitComparator - DESCENDING - ----- - -[NOTE] -==== -`Comparator` implementations are expected to be stateless. -The solver may choose to reuse them in different contexts. -==== - - -[#sortedSelectionByComparatorFactory] -===== [[sortedSelectionBySelectionSorterWeightFactory]]Sorted selection by `ComparatorFactory` - -If you need the entire solution to sort a ``Selector``, use a `ComparatorFactory` instead: - -[source,java,options="nowrap"] ----- -public interface ComparatorFactory { - - Comparator createComparator(Solution_ solution); - -} ----- - -You'll also need to configure it (unless it's annotated on the domain model and automatically applied by the optimization algorithm): - -[source,xml,options="nowrap"] ----- - - PHASE - SORTED - ...MyComparatorFactory - DESCENDING - ----- - -[NOTE] -==== -`ComparatorFactory` implementations are expected to be stateless. -The solver may choose to reuse them in different contexts. -==== - - -[#sortedSelectionBySelectionSorter] -===== Sorted selection by `SelectionSorter` - -Alternatively, you can also use the interface `SelectionSorter` directly: - -[source,java,options="nowrap"] ----- -public interface SelectionSorter { - - void sort(ScoreDirector scoreDirector, List selectionList); - -} ----- - - - -[source,xml,options="nowrap"] ----- - - PHASE - SORTED - ...MyEntitySorter - ----- - -[NOTE] -==== -`SelectionSorter` implementations are expected to be stateless. -The solver may choose to reuse them in different contexts. -==== - - -[#probabilisticSelection] -==== Probabilistic selection - -Probabilistic selection can happen on any Selector in the selector tree, including any ``MoveSelector``, `EntitySelector` or ``ValueSelector``. -It does not work with `cacheType` ``JUST_IN_TIME`` and it only works with ``selectionOrder`` ``PROBABILISTIC``. - -image::optimization-algorithms/overview/probabilisticSelection.png[align="center"] - -Each selection has a ``probabilityWeight``, which determines the chance that selection will be selected: - -[source,java,options="nowrap"] ----- -public interface SelectionProbabilityWeightFactory { - - double createProbabilityWeight(ScoreDirector scoreDirector, T selection); - -} ----- - -[source,xml,options="nowrap"] ----- - - PHASE - PROBABILISTIC - ...MyEntityProbabilityWeightFactoryClass - ----- - -Assume the following entities: lesson A (probabilityWeight 2.0), lesson B (probabilityWeight 0.5) and lesson C (probabilityWeight 0.5). -Then lesson A will be selected four times more than B and C. - -[NOTE] -==== -`SelectionProbabilityWeightFactory` implementations are expected to be stateless. -The solver may choose to reuse them in different contexts. -==== - - -[#limitedSelection] -==== Limited selection - -Selecting all possible moves sometimes does not scale well enough, -especially for xref:optimization-algorithms/construction-heuristics.adoc#constructionHeuristics[construction heuristics], -which don't support xref:optimization-algorithms/local-search.adoc#acceptedCountLimit[`acceptedCountLimit`]. - -To limit the number of selected selection per step, apply a `selectedCountLimit` on the selector: - -[source,xml,options="nowrap"] ----- - - 100 - ----- - -[NOTE] -==== -To scale xref:optimization-algorithms/local-search.adoc#localSearch[Local Search], -setting xref:optimization-algorithms/local-search.adoc#acceptedCountLimit[`acceptedCountLimit`] -is usually better than using ``selectedCountLimit``. -==== - - -[#mimicSelection] -==== Mimic selection (record/replay) - -During mimic selection, one normal selector records its selection and one or multiple other special selectors replay that selection. -The recording selector acts as a normal selector and supports all other configuration properties. -A replaying selector mimics the recording selection and supports no other configuration properties. - -The recording selector needs an ``id``. -A replaying selector must reference a recorder's id with a ``mimicSelectorRef``: - -[source,xml,options="nowrap"] ----- - - - - - - - - - - ----- - -Mimic selection is useful to create <> from two moves that affect the same entity. - - -[#nearbySelectionTeaser] -==== Nearby selection - -[NOTE] -==== -Nearby selection is a commercial feature of xref:enterprise-edition/enterprise-edition.adoc[Timefold Solver Enterprise Edition]. -==== - -Read about nearby selection in the xref:enterprise-edition/enterprise-edition.adoc#nearbySelection[Nearby selection section] -of the xref:enterprise-edition/enterprise-edition.adoc[Enterprise Edition manual]. - - -[#customMoves] -=== Custom moves - -[#whichMoveTypesMightBeMissing] -==== Which move types might be missing in my implementation? - -To determine which move types might be missing in your implementation, -run a xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] __for a short amount of time__ -and xref:using-timefold-solver/benchmarking-and-tweaking.adoc#writeTheOutputSolutionOfBenchmarkRuns[configure it to write the best solutions to disk]. -Take a look at such a best solution: it will likely be a local optima. -Try to figure out if there's a move that could get out of that local optima faster. - -If you find one, implement that coarse-grained move, mix it with the existing moves -and benchmark it against the previous configurations to see if you want to keep it. - - -[#customMovesIntroduction] -==== Custom moves introduction - -Instead of using the generic ``Move``s (such as ``ChangeMove``) you can also implement your own ``Move``. -Generic and custom ``MoveSelector``s can be <> as desired. - -A custom `Move` can be tailored to work to the advantage of your constraints. -For example, in examination scheduling, changing the period of an exam A -would also change the period of all the other exams that need to coincide with exam A. - -A custom `Move` is far more work to implement and much harder to avoid bugs than a generic ``Move``. -After implementing a custom ``Move``, turn on `environmentMode` ``TRACED_FULL_ASSERT`` to check for score corruptions. - - -[#theInterfaceMove] -==== The `Move` interface - -All moves implement the `Move` interface: - -[source,java,options="nowrap"] ----- -public interface Move { - - boolean isMoveDoable(ScoreDirector scoreDirector); - - Move doMove(ScoreDirector scoreDirector); - - ... -} ----- - -To implement a custom move, it's recommended to extend `AbstractMove` instead implementing `Move` directly. -Timefold Solver calls `AbstractMove.doMove(ScoreDirector)`, which calls `doMoveOnGenuineVariables(ScoreDirector)`. -For example, in school timetabling, this move changes one lesson to another timeslot: - -[source,java,options="nowrap"] ----- -public class TimeslotChangeMove extends AbstractMove { - - private Lesson lesson; - private Timeslot toTimeslot; - - public TimeslotChangeMove(Lesson lesson, Timeslot toTimeslot) { - this.lesson = lesson; - this.toTimeslot = toTimeslot; - } - - @Override - protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) { - scoreDirector.beforeVariableChanged(lesson, "timeslot"); - lesson.setTimeslot(toTimeslot); - scoreDirector.afterVariableChanged(lesson, "timeslot"); - } - - // ... - -} ----- - -The implementation must notify the `ScoreDirector` of any changes it makes to planning entity's variables: -Call the `scoreDirector.beforeVariableChanged(Object, String)` and `scoreDirector.afterVariableChanged(Object, String)` -methods directly before and after modifying an entity's planning variable. - -The example move above is a fine-grained move because it changes only one planning variable. -On the other hand, a coarse-grained move changes multiple entities or multiple planning variables -in a single move, usually to avoid breaking hard constraints by making multiple related changes at once. -For example, a swap move is really just two change moves, but it keeps those two changes together. - -[WARNING] -==== -A `Move` can only change/add/remove planning entities, -it must not change any of the problem facts as that will cause score corruption. -Use xref:responding-to-change/responding-to-change.adoc#realTimePlanning[real-time planning] to change problem facts while solving. -==== - -Timefold Solver automatically filters out _non doable moves_ by calling the `isMoveDoable(ScoreDirector)` method on each selected move. -A _non doable move_ is: - -* A move that changes nothing on the current solution. -For example, moving lesson `L1` from timeslot `X` to timeslot `X` is not doable, because it is already there. -* A move that is impossible to do on the current solution. -For example, moving lesson `L1` to timeslot `Q` (when `Q` isn't in the list of lessons) is not doable -because it would assign a planning value that's not inside the planning variable's value range. - -In the school timetabling example, a move which assigns a lesson to the timeslot it's already assigned to is not doable: - -[source,java,options="nowrap"] ----- - @Override - public boolean isMoveDoable(ScoreDirector scoreDirector) { - return !Objects.equals(lesson.getTimeslot(), toTimeslot); - } ----- - -We don't need to check if `toTimeslot` is in the value range, -because we only generate moves for which that is the case. -A move that is currently not doable can become doable when the working solution changes in a later step, -otherwise we probably shouldn't have created it in the first place. - -Each move has an __undo move__: a move which does the exact opposite. -The users do not need to implement this move, as the solver is smart enough to know what to undo based on the move that was done. - -A solver phase might do and undo the same `Move` more than once. -In fact, many solver phases will iteratively do and undo a number of moves to evaluate them, -before selecting one of those and doing that move again (without undoing it the last time). - -Always implement the `toString()` method to keep Timefold Solver's logs readable. -Keep it non-verbose and make it consistent -with xref:optimization-algorithms/move-selector-reference.adoc#moveSelectorReference[the generic moves]: - -[source,java,options="nowrap"] ----- - public String toString() { - return lesson + " {" + lesson.getTimeslot() + " -> " + toTimeslot + "}"; - } ----- - -Optionally, implement the `getSimpleMoveTypeDescription()` method to support -xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarkReportPickedMoveTypeBestScoreDiffOverTimeStatistic[picked move statistics]: - -[source,java,options="nowrap"] ----- - @Override - public String getSimpleMoveTypeDescription() { - return "TimeslotChangeMove(Lesson.timeslot)"; - } ----- - - -===== Custom move: `rebase()` - -For xref:enterprise-edition/enterprise-edition.adoc#multithreadedIncrementalSolving[multi-threaded incremental solving], -the custom move must implement the `rebase()` method: - -[source,java,options="nowrap"] ----- - @Override - public TimeslotChangeMove rebase(ScoreDirector destinationScoreDirector) { - return new TimeslotChangeMove(destinationScoreDirector.lookUpWorkingObject(lesson), - destinationScoreDirector.lookUpWorkingObject(toTimeslot)); - } ----- - -Rebasing a move takes a move generated from one working solution and creates a new move -that does the same change as the original move, -but rewired as if it was generated from the destination working solution. -This allows multi-threaded solving to migrate moves from one thread to another. - -The `lookUpWorkingObject()` method translates a planning entity instance or problem fact instance -from one working solution to that of the destination's working solution. -Internally it often uses a mapping technique based on the xref:using-timefold-solver/modeling-planning-problems.adoc#planningId[planning ID]. - -To rebase lists or arrays in bulk, use `rebaseList()` and `rebaseArray()` on `AbstractMove`. - - -[#customMoveGetPlanningEntitiesAndGetPlanningValues] -===== Custom move: `getPlanningEntities()` and `getPlanningValues()` - -A custom move should also implement the `getPlanningEntities()` and `getPlanningValues()` methods. -Those are used by xref:optimization-algorithms/local-search.adoc#tabuSearch[entity tabu and value tabu] respectively. -They are called after the `Move` has already been done. - -[source,java,options="nowrap"] ----- - @Override - public Collection getPlanningEntities() { - return Collections.singletonList(lesson); - } - - @Override - public Collection getPlanningValues() { - return Collections.singletonList(toTimeslot); - } ----- - -If the `Move` changes multiple planning entities, such as in a swap move, -return all of them in `getPlanningEntities()` -and return all their values (to which they are changing) in ``getPlanningValues()``. - -[source,java,options="nowrap"] ----- - @Override - public Collection getPlanningEntities() { - return Arrays.asList(leftLesson, rightLesson); - } - - @Override - public Collection getPlanningValues() { - return Arrays.asList(leftLesson.getTimeslot(), rightLesson.getTimeslot()); - } ----- - - -[#customMoveEqualsAndHashCode] -===== Custom move: `equals()` and `hashCode()` - -A `Move` must implement the `equals()` and `hashCode()` methods -for xref:optimization-algorithms/local-search.adoc#tabuSearch[move tabu]. -Two moves which make the same change on a solution, should be equal ideally. - -[source,java,options="nowrap"] ----- - @Override - public boolean equals(Object o) { - return o instanceof TimeslotChangeMove other - && lesson.equals(other.lesson) - && toTimeslot.equals(other.toTimeslot); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(lesson) - .append(toTimeslot) - .toHashCode(); - } ----- - -Notice that it checks if the other move is an instance of the same move type. -This `instanceof` check is important because a move are compared to a move of another move type. -For example a `ChangeMove` and `SwapMove` are compared. - -[#generatingCustomMoves] -==== Generating custom moves - -Now, let's generate instances of this custom ``Move`` class. -There are 2 ways: - -[#moveListFactory] -===== `MoveListFactory`: the easy way to generate custom moves - -The easiest way to generate custom moves is by implementing the interface ``MoveListFactory``: - -[source,java,options="nowrap"] ----- -public interface MoveListFactory { - - List createMoveList(Solution_ solution); - -} ----- - -Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): - -[source,xml,options="nowrap"] ----- - - ...MyMoveFactory - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - ...MyMoveFactory - - ... - - ----- - -Because the `MoveListFactory` generates all moves at once in a ``List``, -it does not support `cacheType` ``JUST_IN_TIME``. -Therefore, `moveListFactory` uses `cacheType` ``STEP`` by default and it scales badly. - -To configure values of a `MoveListFactory` dynamically in the solver configuration -(so the xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), -add the `moveListFactoryCustomProperties` element and use xref:using-timefold-solver/configuration.adoc#customPropertiesConfiguration[custom properties]. - -[WARNING] -==== -A custom `MoveListFactory` implementation must ensure that it does not move xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[pinned entities]. -==== - - -[#moveIteratorFactory] -===== ``MoveIteratorFactory``: generate Custom moves just in time - -Use this advanced form to generate custom moves Just In Time -by implementing the `MoveIteratorFactory` interface: - -[source,java,options="nowrap"] ----- -public interface MoveIteratorFactory { - - long getSize(ScoreDirector scoreDirector); - - Iterator createOriginalMoveIterator(ScoreDirector scoreDirector); - - Iterator createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom); - -} ----- - -The `getSize()` method must return an estimation of the size. -It doesn't need to be correct, but it's better too big than too small. -The `createOriginalMoveIterator` method is called if the `selectionOrder` is `ORIGINAL` or if it is cached. -The `createRandomMoveIterator` method is called for `selectionOrder` ``RANDOM`` combined with cacheType ``JUST_IN_TIME``. - -[IMPORTANT] -==== -Don't create a collection (array, list, set or map) of ``Move``s when creating the ``Iterator``: -the whole purpose of `MoveIteratorFactory` over `MoveListFactory` is to create a `Move` just in time -in a custom ``Iterator.next()``. -==== - -For example: - -[source,java,options="nowrap"] ----- -public class PossibleAssignmentsOnlyMoveIteratorFactory implements MoveIteratorFactory { - @Override - public long getSize(ScoreDirector scoreDirector) { - // In this case, we return the exact size, but an estimate can be used - // if it too expensive to calculate or unknown - long totalSize = 0L; - var solution = scoreDirector.getWorkingSolution(); - for (MyEntity entity : solution.getEntities()) { - for (MyPlanningValue value : solution.getValues()) { - if (entity.canBeAssigned(value)) { - totalSize++; - } - } - } - return totalSize; - } - - @Override - public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { - // Only needed if selectionOrder is ORIGINAL or if it is cached - var solution = scoreDirector.getWorkingSolution(); - var entities = solution.getEntities(); - var values = solution.getValues(); - // Assumes each entity has at least one assignable value - var firstEntityIndex = 0; - var firstValueIndex = 0; - while (!entities.get(firstEntityIndex).canBeAssigned(values.get(firstValueIndex))) { - firstValueIndex++; - } - - - return new Iterator<>() { - int nextEntityIndex = firstEntityIndex; - int nextValueIndex = firstValueIndex; - - @Override - public boolean hasNext() { - return nextEntityIndex < entities.size(); - } - - @Override - public MyChangeMove next() { - var selectedEntity = entities.get(nextEntityIndex); - var selectedValue = values.get(nextValueIndex); - nextValueIndex++; - while (nextValueIndex < values.size() && !selectedEntity.canBeAssigned(values.get(nextValueIndex))) { - nextValueIndex++; - } - if (nextValueIndex >= values.size()) { - // value list exhausted, go to next entity - nextEntityIndex++; - if (nextEntityIndex < entities.size()) { - nextValueIndex = 0; - while (nextValueIndex < values.size() && !entities.get(nextEntityIndex).canBeAssigned(values.get(nextValueIndex))) { - // Assumes each entity has at least one assignable value - nextValueIndex++; - } - } - } - return new MyChangeMove(selectedEntity, selectedValue); - } - }; - } - - @Override - public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, - Random workingRandom) { - // Not needed if selectionOrder is ORIGINAL or if it is cached - var solution = scoreDirector.getWorkingSolution(); - var entities = solution.getEntities(); - var values = solution.getValues(); - - return new Iterator<>() { - @Override - public boolean hasNext() { - return !entities.isEmpty(); - } - - @Override - public MyChangeMove next() { - var selectedEntity = entities.get(workingRandom.nextInt(entities.size())); - var selectedValue = values.get(workingRandom.nextInt(values.size())); - while (!selectedEntity.canBeAssigned(selectedValue)) { - // This assumes there at least one value that can be assigned to the selected entity - selectedValue = values.get(workingRandom.nextInt(values.size())); - } - return new MyChangeMove(selectedEntity, selectedValue); - } - }; - } -} ----- - -[NOTE] -==== -The same effect can also be accomplished using <>. -==== - -Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): - -[source,xml,options="nowrap"] ----- - - ... - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - ... - - ... - - ----- - -To configure values of a `MoveIteratorFactory` dynamically in the solver configuration -(so the xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), -add the `moveIteratorFactoryCustomProperties` element and use xref:using-timefold-solver/configuration.adoc#customPropertiesConfiguration[custom properties]. - -[WARNING] -==== -A custom `MoveIteratorFactory` implementation must ensure that it does not move xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[pinned entities]. -==== +Neither of these are part of our public API backwards compatibility guarantees, +although Neighborhoods will eventually become a fully supported API. diff --git a/docs/src/modules/ROOT/pages/quickstart/hello-world/hello-world-quickstart.adoc b/docs/src/modules/ROOT/pages/quickstart/hello-world/hello-world-quickstart.adoc index 6676e69c31b..856f6a3250b 100644 --- a/docs/src/modules/ROOT/pages/quickstart/hello-world/hello-world-quickstart.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/hello-world/hello-world-quickstart.adoc @@ -934,9 +934,9 @@ Also, create a new file in `src/assembly` directory called `jar-with-dependencie [source,xml,options="nowrap"] ---- - + xsi:schemaLocation="https://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> jar-with-dependencies-and-services jar diff --git a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc index 8d57b098ef0..5e4573ad210 100644 --- a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc +++ b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc @@ -650,16 +650,16 @@ This is where _Assignment Recommendation API_ comes in. The Assignment Recommendation API allows you to quickly respond to adhoc changes, while providing a selection of the best available options for fitting the change in the existing schedule. -It doesn't use the full xref:optimization-algorithms/optimization-algorithms.adoc#localSearch[local search algorithm]. +It doesn't use the full xref:optimization-algorithms/local-search.adoc[local search algorithm]. Instead, -it uses a simple xref:optimization-algorithms/optimization-algorithms.adoc#constructionHeuristics[greedy algorithm] +it uses a simple xref:optimization-algorithms/construction-heuristics.adoc[greedy algorithm] together with xref:constraints-and-score/performance.adoc#incrementalScoreCalculationPerformance[incremental calculation]. This combination allows the API to find the best possible fit within the existing solution in a matter of milliseconds, even for large planning problems. Once the customer has accepted one of the available options and the change has been reflected in the solution, -the full xref:optimization-algorithms/optimization-algorithms.adoc#localSearch[local search algorithm] +the full xref:optimization-algorithms/local-search.adoc[local search algorithm] can be used to optimize the entire solution around this change. This would be an example of <>. @@ -714,7 +714,7 @@ If required, <> can be used to optimize [NOTE] ==== Assignment Recommendation API requires the `SolutionManager` to be configured -with a xref:optimization-algorithms/optimization-algorithms.adoc#constructionHeuristics[construction heuristic] as the first phase, +with a xref:optimization-algorithms/construction-heuristics.adoc[construction heuristic] as the first phase, as it uses that construction heuristic to find the best fit. If there are multiple construction heuristics phases in the xref:using-timefold-solver/configuration.adoc#solverConfiguration[solver configuration], or if the first phase is not a construction heuristic @@ -782,7 +782,7 @@ But problems with the same publication deadline, solved by different organizatio are also initially better off with multi-stage planning, because of Conway's law and the high risk associated with unifying such groups. -Similarly to xref:optimization-algorithms/optimization-algorithms.adoc#partitionedSearch[Partitioned Search], multi-stage planning leads to suboptimal results. +Similarly to xref:enterprise-edition/enterprise-edition.adoc#partitionedSearch[Partitioned Search], multi-stage planning leads to suboptimal results. Nevertheless, it might be beneficial in order to simplify the maintenance, ownership, and help to start a project. Do not confuse multi-stage planning with xref:optimization-algorithms/optimization-algorithms.adoc#solverPhase[multi-phase solving]. 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 b212e089b96..f59888b241b 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 @@ -25,7 +25,7 @@ If instead you are looking for guidance on how to create a good domain model, re [NOTE] ==== -If you have never tried Timefold Solver before, we recommend following our xref:quickStartOverview[Getting Started guides] for a practical introduction. +If you have never tried Timefold Solver before, we recommend following our xref:quickstart/overview.adoc[Getting Started guides] for a practical introduction. ==== A planning domain model typically consist of the following elements: @@ -416,7 +416,7 @@ public class VisitComparator implements Comparator { Alternatively, you can also set a `comparatorFactoryClass` to the `@PlanningEntity` annotation, so that you have access to the rest of the problem facts from the solution too. -See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] for more information. +See xref:optimization-algorithms/move-selector-reference.adoc#sortedSelection[sorted selection] for more information. [IMPORTANT] ==== @@ -1519,7 +1519,7 @@ Alternatively, you can also set a `comparatorFactoryClass` to the `@PlanningVariable` or `@PlanningListVariable` annotations, so you have access to the rest of the problem facts from the solution too. -See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] for more information. +See xref:optimization-algorithms/move-selector-reference.adoc#sortedSelection[sorted selection] for more information. [IMPORTANT] ==== diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java index 58e156c662b..f6b9cf0440c 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java @@ -22,13 +22,13 @@ import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintFactory; import ai.timefold.solver.core.api.score.stream.ConstraintProvider; -import ai.timefold.solver.core.impl.heuristic.move.DummyMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveListFactory; -import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.partitionedsearch.partitioner.SolutionPartitioner; +import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -236,26 +236,27 @@ public boolean accept(ScoreDirector scoreDirector, TestdataEnt } public static class DummyChangeMoveFilter - implements SelectionFilter> { + implements SelectionFilter> { @Override - public boolean accept(ScoreDirector scoreDirector, ChangeMove selection) { + public boolean accept(ScoreDirector scoreDirector, + SelectorBasedChangeMove selection) { return false; } } - public static class DummyMoveIteratorFactory implements MoveIteratorFactory { + public static class DummyMoveIteratorFactory implements MoveIteratorFactory { @Override public long getSize(ScoreDirector scoreDirector) { return 0; } @Override - public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { + public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { return null; } @Override - public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, + public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, RandomGenerator workingRandom) { return null; } diff --git a/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypebestscore/PickedMoveTypeBestScoreDiffStatisticPoint.java b/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypebestscore/PickedMoveTypeBestScoreDiffStatisticPoint.java index 602dcc176e1..7ade27cddc6 100644 --- a/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypebestscore/PickedMoveTypeBestScoreDiffStatisticPoint.java +++ b/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypebestscore/PickedMoveTypeBestScoreDiffStatisticPoint.java @@ -2,14 +2,14 @@ import ai.timefold.solver.benchmark.impl.statistic.StatisticPoint; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.heuristic.move.CompositeMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedCompositeMove; +import ai.timefold.solver.core.preview.api.move.Move; public class PickedMoveTypeBestScoreDiffStatisticPoint extends StatisticPoint { private final long timeMillisSpent; /** - * Not a {@link Class}{@code <}{@link Move}{@code >} because {@link CompositeMove}s need to be atomized + * Not a {@link Class}{@code <}{@link Move}{@code >} because {@link SelectorBasedCompositeMove}s need to be atomized * and because that {@link Class} might no longer exist when the benchmark aggregator runs. */ private final String moveType; diff --git a/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypestepscore/PickedMoveTypeStepScoreDiffStatisticPoint.java b/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypestepscore/PickedMoveTypeStepScoreDiffStatisticPoint.java index 83978fcf878..9ec932211b5 100644 --- a/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypestepscore/PickedMoveTypeStepScoreDiffStatisticPoint.java +++ b/tools/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/statistic/subsingle/pickedmovetypestepscore/PickedMoveTypeStepScoreDiffStatisticPoint.java @@ -2,14 +2,14 @@ import ai.timefold.solver.benchmark.impl.statistic.StatisticPoint; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.heuristic.move.CompositeMove; -import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedCompositeMove; +import ai.timefold.solver.core.preview.api.move.Move; public class PickedMoveTypeStepScoreDiffStatisticPoint extends StatisticPoint { private final long timeMillisSpent; /** - * Not a {@link Class}{@code <}{@link Move}{@code >} because {@link CompositeMove}s need to be atomized + * Not a {@link Class}{@code <}{@link Move}{@code >} because {@link SelectorBasedCompositeMove}s need to be atomized * and because that {@link Class} might no longer exist when the benchmark aggregator runs. */ private final String moveType;