Skip to content

Commit 3a178d5

Browse files
committed
chore: address more comments
1 parent 91bfb3b commit 3a178d5

4 files changed

Lines changed: 268 additions & 0 deletions

File tree

core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,26 @@ void solveWithListAndBasicVariables() {
436436
.isEmpty();
437437
}
438438

439+
@Test
440+
void solvePinnedWithListAndBasicVariables() {
441+
var solverConfig = PlannerTestUtils.buildSolverConfig(
442+
TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class,
443+
TestdataListMultiVarOtherValue.class)
444+
.withPhases(new ConstructionHeuristicPhaseConfig()
445+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
446+
.withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class);
447+
448+
var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
449+
// Pin the first entity
450+
problem.getEntityList().get(0).setPinned(true);
451+
problem.getEntityList().get(0).setPinnedIndex(0);
452+
var solution = PlannerTestUtils.solve(solverConfig, problem);
453+
// The first entity should remain unchanged
454+
assertThat(solution.getEntityList().get(0).getBasicValue()).isNull();
455+
assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNull();
456+
assertThat(solution.getEntityList().get(0).getValueList()).isEmpty();
457+
}
458+
439459
@Test
440460
void solveUnassignedWithListAndBasicVariables() {
441461
var solverConfig = PlannerTestUtils.buildSolverConfig(
@@ -462,6 +482,36 @@ void solveUnassignedWithListAndBasicVariables() {
462482
.hasSize(2);
463483
}
464484

485+
@Test
486+
void solvePinnedAndUnassignedWithListAndBasicVariables() {
487+
var solverConfig = PlannerTestUtils.buildSolverConfig(
488+
TestdataUnassignedListMultiVarSolution.class, TestdataUnassignedListMultiVarEntity.class)
489+
.withPhases(new ConstructionHeuristicPhaseConfig()
490+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
491+
.withEasyScoreCalculatorClass(TestdataUnassignedListMultiVarEasyScoreCalculator.class);
492+
493+
var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
494+
// Pin the first entity
495+
problem.getEntityList().get(0).setPinned(true);
496+
problem.getEntityList().get(0).setPinnedIndex(0);
497+
problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0));
498+
problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0));
499+
problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0)));
500+
// Block values and make the basic and list variables unassigned
501+
problem.getValueList().get(0).setBlocked(true);
502+
problem.getValueList().get(1).setBlocked(true);
503+
problem.getOtherValueList().get(0).setBlocked(true);
504+
problem.getOtherValueList().get(1).setBlocked(true);
505+
var solution = PlannerTestUtils.solve(solverConfig, problem);
506+
// The first entity should remain unchanged
507+
assertThat(solution.getEntityList().get(0).getBasicValue()).isNotNull();
508+
assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull();
509+
assertThat(solution.getEntityList().get(0).getValueList()).hasSize(1);
510+
assertThat(solution.getEntityList().get(1).getBasicValue()).isNull();
511+
assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull();
512+
assertThat(solution.getEntityList().get(1).getValueList()).isEmpty();
513+
}
514+
465515
@Test
466516
void solveCustomConfigurationMultiEntityWithListAndBasicVariables() {
467517
var valueSelectorConfig = new ValueSelectorConfig("valueList")

core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedMultiplePlacerFactoryTest.java

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,84 @@ void testPlacersForConstructionHeuristic() {
143143
assertThat(placerIterator.hasNext()).isFalse();
144144
}
145145

146+
@Test
147+
void testPinnedPlacersForConstructionHeuristic() {
148+
var solutionDescriptor = TestdataListMultiVarSolution.buildSolutionDescriptor();
149+
var configPolicy = new HeuristicConfigPolicy.Builder<TestdataListMultiVarSolution>()
150+
.withEnvironmentMode(PHASE_ASSERT)
151+
.withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY }))
152+
.withSolutionDescriptor(solutionDescriptor)
153+
.withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE)
154+
.withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE)
155+
.withReinitializeVariableFilterEnabled(true)
156+
.withInitializedChainedValueFilterEnabled(true)
157+
.withUnassignedValuesAllowed(true)
158+
.withRandom(new Random(0))
159+
.build();
160+
var valueSelectorConfig = new ValueSelectorConfig("valueList")
161+
.withId("valueList");
162+
var mimicReplayingValueSelectorConfig = new ValueSelectorConfig()
163+
.withMimicSelectorRef("valueList")
164+
.withVariableName("valueList");
165+
var valuePlacerConfig = new QueuedValuePlacerConfig()
166+
.withValueSelectorConfig(valueSelectorConfig)
167+
.withMoveSelectorConfig(new ListChangeMoveSelectorConfig()
168+
.withValueSelectorConfig(mimicReplayingValueSelectorConfig));
169+
var entityPlacerConfig = new QueuedEntityPlacerConfig();
170+
var placerConfig = new QueuedMultiplePlacerConfig()
171+
.withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig));
172+
var placer = EntityPlacerFactory.<TestdataListMultiVarSolution> create(placerConfig).buildEntityPlacer(configPolicy);
173+
174+
var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
175+
// Pin the first entity
176+
problem.getEntityList().get(0).setPinned(true);
177+
problem.getEntityList().get(0).setPinnedIndex(2);
178+
problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0));
179+
problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0));
180+
problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0)));
181+
182+
var solverScope = mock(SolverScope.class);
183+
var scoreDirector = mock(InnerScoreDirector.class);
184+
var random = new Random(0L);
185+
when(solverScope.getScoreDirector()).thenReturn(scoreDirector);
186+
when(solverScope.getWorkingRandom()).thenReturn(random);
187+
when(scoreDirector.getWorkingSolution()).thenReturn(problem);
188+
when(scoreDirector.getWorkingSolution()).thenReturn(problem);
189+
when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor);
190+
191+
var supplyManager = VariableListenerSupport.create(scoreDirector);
192+
when(scoreDirector.getSupplyManager()).thenReturn(supplyManager);
193+
supplyManager.linkVariableListeners();
194+
supplyManager.resetWorkingSolution();
195+
196+
placer.solvingStarted(solverScope);
197+
var phaseScope = mock(AbstractPhaseScope.class);
198+
when(phaseScope.getScoreDirector()).thenReturn(scoreDirector);
199+
placer.phaseStarted(phaseScope);
200+
201+
var placerIterator = placer.iterator();
202+
203+
// Step 1
204+
// 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue
205+
// 2 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue
206+
// 3 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue
207+
// 4 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue
208+
assertThat(placerIterator.hasNext()).isTrue();
209+
var counter = new MutableInt();
210+
placerIterator.next().iterator().forEachRemaining(move -> counter.increment());
211+
assertThat(counter.intValue()).isEqualTo(4);
212+
213+
// Accept the move -> 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue
214+
problem.getEntityList().get(1).setValueList(List.of(problem.getValueList().get(1)));
215+
problem.getEntityList().get(1).setBasicValue(problem.getOtherValueList().get(0));
216+
problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0));
217+
// Update all variables
218+
supplyManager.resetWorkingSolution();
219+
var stepScope = mock(AbstractStepScope.class);
220+
placer.stepEnded(stepScope);
221+
assertThat(placerIterator.hasNext()).isFalse();
222+
}
223+
146224
@Test
147225
void testUnassignedPlacersForConstructionHeuristic() {
148226
var solutionDescriptor = TestdataUnassignedListMultiVarSolution.buildSolutionDescriptor();
@@ -303,4 +381,96 @@ void testUnassignedPlacersForConstructionHeuristic() {
303381
placerIterator.next().iterator().forEachRemaining(move -> counter.increment());
304382
assertThat(counter.intValue()).isEqualTo(18);
305383
}
384+
385+
@Test
386+
void testPinnedUnassignedPlacersForConstructionHeuristic() {
387+
var solutionDescriptor = TestdataUnassignedListMultiVarSolution.buildSolutionDescriptor();
388+
var configPolicy = new HeuristicConfigPolicy.Builder<TestdataUnassignedListMultiVarSolution>()
389+
.withEnvironmentMode(PHASE_ASSERT)
390+
.withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY }))
391+
.withSolutionDescriptor(solutionDescriptor)
392+
.withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE)
393+
.withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE)
394+
.withReinitializeVariableFilterEnabled(true)
395+
.withInitializedChainedValueFilterEnabled(true)
396+
.withUnassignedValuesAllowed(true)
397+
.withRandom(new Random(0))
398+
.build();
399+
var valueSelectorConfig = new ValueSelectorConfig("valueList")
400+
.withId("valueList");
401+
var mimicReplayingValueSelectorConfig = new ValueSelectorConfig()
402+
.withMimicSelectorRef("valueList")
403+
.withVariableName("valueList");
404+
var valuePlacerConfig = new QueuedValuePlacerConfig()
405+
.withValueSelectorConfig(valueSelectorConfig)
406+
.withMoveSelectorConfig(new ListChangeMoveSelectorConfig()
407+
.withValueSelectorConfig(mimicReplayingValueSelectorConfig));
408+
var entityPlacerConfig = new QueuedEntityPlacerConfig();
409+
var placerConfig = new QueuedMultiplePlacerConfig()
410+
.withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig));
411+
var placer = EntityPlacerFactory.<TestdataUnassignedListMultiVarSolution> create(placerConfig)
412+
.buildEntityPlacer(configPolicy);
413+
414+
var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
415+
// Pin the first entity
416+
problem.getEntityList().get(0).setPinned(true);
417+
problem.getEntityList().get(0).setPinnedIndex(2);
418+
problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0));
419+
problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0));
420+
problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0)));
421+
422+
var solverScope = mock(SolverScope.class);
423+
var scoreDirector = mock(InnerScoreDirector.class);
424+
var random = new Random(0L);
425+
when(solverScope.getScoreDirector()).thenReturn(scoreDirector);
426+
when(solverScope.getWorkingRandom()).thenReturn(random);
427+
when(scoreDirector.getWorkingSolution()).thenReturn(problem);
428+
when(scoreDirector.getWorkingSolution()).thenReturn(problem);
429+
when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor);
430+
431+
var supplyManager = VariableListenerSupport.create(scoreDirector);
432+
when(scoreDirector.getSupplyManager()).thenReturn(supplyManager);
433+
supplyManager.linkVariableListeners();
434+
supplyManager.resetWorkingSolution();
435+
436+
placer.solvingStarted(solverScope);
437+
var phaseScope = mock(AbstractPhaseScope.class);
438+
when(phaseScope.getScoreDirector()).thenReturn(scoreDirector);
439+
placer.phaseStarted(phaseScope);
440+
441+
var placerIterator = placer.iterator();
442+
// Step 1
443+
// 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue
444+
// 2 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue
445+
// 3 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue
446+
// 4 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue
447+
// 5 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue
448+
// 6 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue
449+
// 7 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue
450+
// 8 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue
451+
// 9 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue
452+
// 10 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue
453+
// 11 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue
454+
// 12 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue
455+
assertThat(placerIterator.hasNext()).isTrue();
456+
var counter = new MutableInt();
457+
placerIterator.next().iterator().forEachRemaining(move -> counter.increment());
458+
assertThat(counter.intValue()).isEqualTo(12);
459+
460+
// Accept the move - 7 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue
461+
problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0));
462+
// Update all variables
463+
supplyManager.resetWorkingSolution();
464+
var stepScope = mock(AbstractStepScope.class);
465+
placer.stepEnded(stepScope);
466+
counter.setValue(0);
467+
// 1 = Generated Value 1 -> Entity 1[0] - null -> secondBasicValue
468+
// 2 = Generated Value 1 -> Entity 1[0] - Generated Other Value 0 -> secondBasicValue
469+
// 3 = Generated Value 1 -> Entity 1[0] - Generated Other Value 1 -> secondBasicValue
470+
// 4 = NoChange - null -> secondBasicValue
471+
// 5 = NoChange - Generated Other Value 0 -> secondBasicValue
472+
// 6 = NoChange - Generated Other Value 1 -> secondBasicValue
473+
placerIterator.next().iterator().forEachRemaining(move -> counter.increment());
474+
assertThat(counter.intValue()).isEqualTo(6);
475+
}
306476
}

core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEntity.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.util.List;
55

66
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
7+
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
8+
import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex;
79
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
810
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
911
import ai.timefold.solver.core.testdomain.TestdataObject;
@@ -20,6 +22,12 @@ public class TestdataListMultiVarEntity extends TestdataObject {
2022
@PlanningListVariable(valueRangeProviderRefs = "valueRange")
2123
private List<TestdataListMultiVarValue> valueList;
2224

25+
@PlanningPin
26+
private boolean pinned = false;
27+
28+
@PlanningPinToIndex
29+
private int pinnedIndex = 0;
30+
2331
public TestdataListMultiVarEntity() {
2432
// Required for cloner
2533
}
@@ -52,4 +60,20 @@ public List<TestdataListMultiVarValue> getValueList() {
5260
public void setValueList(List<TestdataListMultiVarValue> valueList) {
5361
this.valueList = valueList;
5462
}
63+
64+
public boolean isPinned() {
65+
return pinned;
66+
}
67+
68+
public void setPinned(boolean pinned) {
69+
this.pinned = pinned;
70+
}
71+
72+
public int getPinnedIndex() {
73+
return pinnedIndex;
74+
}
75+
76+
public void setPinnedIndex(int pinnedIndex) {
77+
this.pinnedIndex = pinnedIndex;
78+
}
5579
}

core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEntity.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.util.List;
55

66
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
7+
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
8+
import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex;
79
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
810
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
911
import ai.timefold.solver.core.testdomain.TestdataObject;
@@ -20,6 +22,12 @@ public class TestdataUnassignedListMultiVarEntity extends TestdataObject {
2022
@PlanningListVariable(valueRangeProviderRefs = "valueRange", allowsUnassignedValues = true)
2123
private List<TestdataUnassignedListMultiVarValue> valueList;
2224

25+
@PlanningPin
26+
private boolean pinned = false;
27+
28+
@PlanningPinToIndex
29+
private int pinnedIndex = 0;
30+
2331
public TestdataUnassignedListMultiVarEntity() {
2432
// Required for cloner
2533
}
@@ -52,4 +60,20 @@ public List<TestdataUnassignedListMultiVarValue> getValueList() {
5260
public void setValueList(List<TestdataUnassignedListMultiVarValue> valueList) {
5361
this.valueList = valueList;
5462
}
63+
64+
public boolean isPinned() {
65+
return pinned;
66+
}
67+
68+
public void setPinned(boolean pinned) {
69+
this.pinned = pinned;
70+
}
71+
72+
public int getPinnedIndex() {
73+
return pinnedIndex;
74+
}
75+
76+
public void setPinnedIndex(int pinnedIndex) {
77+
this.pinnedIndex = pinnedIndex;
78+
}
5579
}

0 commit comments

Comments
 (0)