From e7fae26c95b78386f1c5134441ef88d214d21e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederico=20Gon=C3=A7alves?= Date: Mon, 8 Jul 2024 09:43:19 -0300 Subject: [PATCH] fix: only include tail chain move when using a single var (#936) --- .../DefaultLocalSearchPhaseFactory.java | 2 +- .../DefaultLocalSearchPhaseTest.java | 12 ++++++++++ .../chained/TestdataChainedSolution.java | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java index 9cca51efe8..3f82dbf0e3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java @@ -219,7 +219,7 @@ private UnionMoveSelectorConfig determineDefaultMoveSelectorConfig(HeuristicConf .withMoveSelectors(new ListChangeMoveSelectorConfig(), new ListSwapMoveSelectorConfig(), new KOptListMoveSelectorConfig()); } else if (listVariableDescriptor == null) { // We only have basic variables. - if (hasChainedVariable) { + if (hasChainedVariable && basicVariableDescriptorList.size() == 1) { return new UnionMoveSelectorConfig() .withMoveSelectors(new ChangeMoveSelectorConfig(), new SwapMoveSelectorConfig(), new TailChainSwapMoveSelectorConfig()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseTest.java b/core/src/test/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseTest.java index 80c47969dd..396c4d8073 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseTest.java @@ -14,6 +14,8 @@ import ai.timefold.solver.core.impl.testdata.domain.TestdataEntity; import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution; import ai.timefold.solver.core.impl.testdata.domain.TestdataValue; +import ai.timefold.solver.core.impl.testdata.domain.chained.TestdataChainedEntity; +import ai.timefold.solver.core.impl.testdata.domain.chained.TestdataChainedSolution; import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListEntity; import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListSolution; import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListValue; @@ -244,6 +246,16 @@ void solveListVariable() { assertThat(solution).isNotNull(); } + @Test + void solveMultiVarChainedVariable() { + var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataChainedSolution.class, TestdataChainedEntity.class); + + var solution = TestdataChainedSolution.generateUninitializedSolution(6, 2); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + } + @Test void solveListVariableWithExternalizedInverseAndIndexSupplies() { var solverConfig = PlannerTestUtils.buildSolverConfig( diff --git a/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/chained/TestdataChainedSolution.java b/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/chained/TestdataChainedSolution.java index 6b5b46986b..def3d90f25 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/chained/TestdataChainedSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/chained/TestdataChainedSolution.java @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.testdata.domain.chained; import java.util.List; +import java.util.stream.IntStream; import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; import ai.timefold.solver.core.api.domain.solution.PlanningScore; @@ -74,4 +75,25 @@ public void setScore(SimpleScore score) { // Complex methods // ************************************************************************ + public static TestdataChainedSolution generateUninitializedSolution(int valueCount, int entityCount) { + return generateSolution(valueCount, entityCount); + } + + private static TestdataChainedSolution generateSolution(int valueCount, int entityCount) { + List entityList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataChainedEntity("Generated Entity " + i)) + .toList(); + List anchorList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataChainedAnchor("Generated Anchor " + i)) + .toList(); + List valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataValue("Generated Value " + i)) + .toList(); + TestdataChainedSolution solution = new TestdataChainedSolution(); + solution.setChainedEntityList(entityList); + solution.setChainedAnchorList(anchorList); + solution.setUnchainedValueList(valueList); + return solution; + } + }