Skip to content

Commit c551d02

Browse files
committed
test: add more tests
1 parent 23a81db commit c551d02

20 files changed

Lines changed: 405 additions & 99 deletions

core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.Collection;
44
import java.util.List;
55
import java.util.Objects;
6-
import java.util.stream.Collectors;
76

87
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
98
import ai.timefold.solver.core.config.AbstractConfig;
@@ -19,7 +18,7 @@ public abstract class AbstractFromConfigFactory<Solution_, Config_ extends Abstr
1918

2019
protected final Config_ config;
2120

22-
public AbstractFromConfigFactory(Config_ config) {
21+
protected AbstractFromConfigFactory(Config_ config) {
2322
this.config = config;
2423
}
2524

@@ -76,6 +75,21 @@ protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescrip
7675
return entityDescriptors.iterator().next();
7776
}
7877

78+
protected EntityDescriptor<Solution_>
79+
getTheOnlyEntityDescriptorWithBasicVariables(SolutionDescriptor<Solution_> solutionDescriptor) {
80+
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
81+
.stream()
82+
.filter(EntityDescriptor::hasAnyGenuineBasicVariables)
83+
.toList();
84+
if (entityDescriptors.size() != 1) {
85+
throw new IllegalArgumentException("The config (" + config
86+
+ ") has no entityClass configured and because there are multiple in the entityClassSet ("
87+
+ solutionDescriptor.getEntityClassSet()
88+
+ ") defining basic variables, it cannot be deduced automatically.");
89+
}
90+
return entityDescriptors.iterator().next();
91+
}
92+
7993
protected GenuineVariableDescriptor<Solution_> deduceGenuineVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor,
8094
String variableName) {
8195
return variableName == null
@@ -126,6 +140,6 @@ protected List<GenuineVariableDescriptor<Solution_>> deduceVariableDescriptorLis
126140
+ ") has a variableNameInclude (" + variableNameInclude
127141
+ ") which does not exist in the entity (" + entityDescriptor.getEntityClass()
128142
+ ")'s variableDescriptorList (" + variableDescriptorList + ").")))
129-
.collect(Collectors.toList());
143+
.toList();
130144
}
131145
}

core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public QueuedEntityPlacer<Solution_> buildEntityPlacer(HeuristicConfigPolicy<Sol
106106
public EntitySelectorConfig buildEntitySelectorConfig(HeuristicConfigPolicy<Solution_> configPolicy) {
107107
var entitySelectorConfig = config.getEntitySelectorConfig();
108108
if (entitySelectorConfig == null) {
109-
var entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor());
109+
var entityDescriptor = getTheOnlyEntityDescriptorWithBasicVariables(configPolicy.getSolutionDescriptor());
110110
entitySelectorConfig = getDefaultEntitySelectorConfigForEntity(configPolicy, entityDescriptor);
111111
} else {
112112
// The default phase configuration generates the entity selector config without an updated version of the configuration policy.

core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,14 @@ public boolean hasAnyGenuineVariables() {
699699
return !effectiveGenuineVariableDescriptorMap.isEmpty();
700700
}
701701

702+
public boolean hasAnyGenuineBasicVariables() {
703+
if (!isGenuine()) {
704+
return false;
705+
}
706+
return getDeclaredGenuineVariableDescriptors().stream()
707+
.anyMatch(descriptor -> !descriptor.isListVariable());
708+
}
709+
702710
public boolean hasAnyGenuineListVariables() {
703711
if (!isGenuine()) {
704712
return false;

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig;
1313
import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig;
1414
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig;
15+
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
1516
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
1617
import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor;
1718
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
@@ -71,8 +72,18 @@ protected MoveSelectorConfig<?> buildUnfoldedMoveSelectorConfig(HeuristicConfigP
7172
: EntitySelectorFactory.<Solution_> create(destinationEntitySelectorConfig)
7273
.extractEntityDescriptor(configPolicy);
7374
var entityDescriptors =
74-
onlyEntityDescriptor == null ? configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors()
75+
onlyEntityDescriptor == null ? configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors().stream()
76+
// We need to filter the entity that defines the list variable
77+
.filter(EntityDescriptor::hasAnyGenuineListVariables)
78+
.toList()
7579
: Collections.singletonList(onlyEntityDescriptor);
80+
81+
if (entityDescriptors.isEmpty()) {
82+
throw new IllegalArgumentException(
83+
"The listChangeMoveSelector (%s) cannot unfold because there are no planning list variables."
84+
.formatted(config));
85+
}
86+
7687
if (entityDescriptors.size() > 1) {
7788
throw new IllegalArgumentException("""
7889
The listChangeMoveSelector (%s) cannot unfold when there are multiple entities (%s).
@@ -123,11 +134,6 @@ The listChangeMoveSelector (%s) is configured to use a planning variable (%s), \
123134
.map(variableDescriptor -> ((ListVariableDescriptor<Solution_>) variableDescriptor))
124135
.toList());
125136
}
126-
if (variableDescriptorList.isEmpty()) {
127-
throw new IllegalArgumentException(
128-
"The listChangeMoveSelector (%s) cannot unfold because there are no planning list variables."
129-
.formatted(config));
130-
}
131137
if (variableDescriptorList.size() > 1) {
132138
throw new IllegalArgumentException(
133139
"The listChangeMoveSelector (%s) cannot unfold because there are multiple planning list variables."

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import ai.timefold.solver.core.config.constructionheuristic.placer.PooledEntityPlacerConfig;
2121
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig;
2222
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig;
23+
import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig;
24+
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig;
2325
import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig;
2426
import ai.timefold.solver.core.config.solver.monitoring.SolverMetric;
27+
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;
2528
import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter;
2629
import ai.timefold.solver.core.impl.solver.DefaultSolver;
2730
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
@@ -31,10 +34,20 @@
3134
import ai.timefold.solver.core.testdomain.list.TestdataListEntity;
3235
import ai.timefold.solver.core.testdomain.list.TestdataListSolution;
3336
import ai.timefold.solver.core.testdomain.list.TestdataListValue;
37+
import ai.timefold.solver.core.testdomain.list.TestdataListVarEasyScoreCalculator;
3438
import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEasyScoreCalculator;
3539
import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEntity;
3640
import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListSolution;
3741
import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListValue;
42+
import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntityEasyScoreCalculator;
43+
import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntityFirstEntity;
44+
import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntitySecondEntity;
45+
import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntitySolution;
46+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarEasyScoreCalculator;
47+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarEntity;
48+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarOtherValue;
49+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarSolution;
50+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarValue;
3851
import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedEntity;
3952
import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution;
4053
import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity;
@@ -388,4 +401,121 @@ void failWithPooledEntityPlacers() {
388401
"The Construction Heuristic configuration (ConstructionHeuristicPhaseConfig) does not support multiple configurations when using the pooled placer configuration PooledEntityPlacerConfig.");
389402
}
390403

404+
@Test
405+
void failMultiEntityWithListAndBasicVariables() {
406+
var solverConfig = PlannerTestUtils.buildSolverConfig(
407+
TestdataListMultiEntitySolution.class, TestdataListMultiEntityFirstEntity.class,
408+
TestdataListMultiEntitySecondEntity.class)
409+
.withPhases(new ConstructionHeuristicPhaseConfig()
410+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
411+
.withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class);
412+
413+
var problem = TestdataListMultiEntitySolution.generateUninitializedSolution(2, 2, 2);
414+
assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem))
415+
.hasMessageContaining("has no entityClass configured and because there are multiple in the entityClassSet")
416+
.hasMessageContaining("it cannot be deduced automatically");
417+
}
418+
419+
@Test
420+
void solveWithListAndBasicVariables() {
421+
var solverConfig = PlannerTestUtils.buildSolverConfig(
422+
TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class,
423+
TestdataListMultiVarOtherValue.class)
424+
.withPhases(new ConstructionHeuristicPhaseConfig()
425+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
426+
.withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class);
427+
428+
var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
429+
var solution = PlannerTestUtils.solve(solverConfig, problem);
430+
assertThat(solution.getEntityList().stream()
431+
.filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty()))
432+
.isEmpty();
433+
}
434+
435+
@Test
436+
void solveCustomConfigurationMultiEntityWithListAndBasicVariables() {
437+
var valueSelectorConfig = new ValueSelectorConfig("valueList")
438+
.withId("valueList");
439+
var mimicReplayingValueSelectorConfig = new ValueSelectorConfig()
440+
.withMimicSelectorRef("valueList")
441+
.withVariableName("valueList");
442+
var valuePlacerConfig = new QueuedValuePlacerConfig()
443+
.withValueSelectorConfig(valueSelectorConfig)
444+
.withMoveSelectorConfig(new ListChangeMoveSelectorConfig()
445+
.withValueSelectorConfig(mimicReplayingValueSelectorConfig))
446+
.withEntityClass(TestdataListMultiEntityFirstEntity.class);
447+
var entityPlacerConfig = new QueuedEntityPlacerConfig();
448+
449+
var solverConfig = PlannerTestUtils.buildSolverConfig(
450+
TestdataListMultiEntitySolution.class, TestdataListMultiEntityFirstEntity.class,
451+
TestdataListMultiEntitySecondEntity.class)
452+
.withPhases(new ConstructionHeuristicPhaseConfig()
453+
.withEntityPlacerConfigList(valuePlacerConfig, entityPlacerConfig)
454+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
455+
.withEasyScoreCalculatorClass(TestdataListMultiEntityEasyScoreCalculator.class);
456+
457+
var problem = TestdataListMultiEntitySolution.generateUninitializedSolution(2, 2, 2);
458+
var solution = PlannerTestUtils.solve(solverConfig, problem);
459+
assertThat(solution.getEntityList().stream()
460+
.filter(e -> e.getValueList().isEmpty()))
461+
.isEmpty();
462+
assertThat(solution.getOtherEntityList().stream()
463+
.filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null))
464+
.isEmpty();
465+
}
466+
467+
@Test
468+
void solveCustomConfigurationWithListAndBasicVariables() {
469+
var valueSelectorConfig = new ValueSelectorConfig("valueList")
470+
.withId("valueList");
471+
var mimicReplayingValueSelectorConfig = new ValueSelectorConfig()
472+
.withMimicSelectorRef("valueList")
473+
.withVariableName("valueList");
474+
var valuePlacerConfig = new QueuedValuePlacerConfig()
475+
.withValueSelectorConfig(valueSelectorConfig)
476+
.withMoveSelectorConfig(new ListChangeMoveSelectorConfig()
477+
.withValueSelectorConfig(mimicReplayingValueSelectorConfig));
478+
var entityPlacerConfig = new QueuedEntityPlacerConfig();
479+
480+
var solverConfig = PlannerTestUtils.buildSolverConfig(
481+
TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class,
482+
TestdataListMultiVarOtherValue.class)
483+
.withPhases(new ConstructionHeuristicPhaseConfig()
484+
.withEntityPlacerConfigList(valuePlacerConfig, entityPlacerConfig)
485+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
486+
.withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class);
487+
488+
var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2);
489+
var solution = PlannerTestUtils.solve(solverConfig, problem);
490+
assertThat(solution.getEntityList().stream()
491+
.filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty()))
492+
.isEmpty();
493+
}
494+
495+
@Test
496+
void solveCustomConfigurationWithListVariables() {
497+
var valueSelectorConfig = new ValueSelectorConfig("valueList")
498+
.withId("valueList");
499+
var mimicReplayingValueSelectorConfig = new ValueSelectorConfig()
500+
.withMimicSelectorRef("valueList")
501+
.withVariableName("valueList");
502+
var valuePlacerConfig = new QueuedValuePlacerConfig()
503+
.withValueSelectorConfig(valueSelectorConfig)
504+
.withMoveSelectorConfig(new ListChangeMoveSelectorConfig()
505+
.withValueSelectorConfig(mimicReplayingValueSelectorConfig));
506+
507+
var solverConfig = PlannerTestUtils.buildSolverConfig(
508+
TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class)
509+
.withPhases(new ConstructionHeuristicPhaseConfig()
510+
.withEntityPlacerConfigList(valuePlacerConfig)
511+
.withTerminationConfig(new TerminationConfig().withStepCountLimit(16)))
512+
.withEasyScoreCalculatorClass(TestdataListVarEasyScoreCalculator.class);
513+
514+
var problem = TestdataListSolution.generateUninitializedSolution(2, 2);
515+
var solution = PlannerTestUtils.solve(solverConfig, problem);
516+
assertThat(solution.getEntityList().stream()
517+
.filter(e -> e.getValueList().isEmpty()))
518+
.isEmpty();
519+
}
520+
391521
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend;
2727
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
2828
import ai.timefold.solver.core.impl.util.MutableInt;
29-
import ai.timefold.solver.core.testdomain.multivar.list.TestdataListMultiVarSolution;
29+
import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarSolution;
3030

3131
import org.junit.jupiter.api.Test;
3232

0 commit comments

Comments
 (0)