Skip to content

Commit a43b552

Browse files
committed
feat: enable mixed models for the default LS moves
1 parent ad2f9e0 commit a43b552

12 files changed

Lines changed: 433 additions & 414 deletions

File tree

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

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ai.timefold.solver.core.impl;
22

3-
import java.util.Collection;
43
import java.util.List;
54
import java.util.Objects;
65

@@ -24,8 +23,8 @@ protected AbstractFromConfigFactory(Config_ config) {
2423

2524
public static <Solution_> EntitySelectorConfig getDefaultEntitySelectorConfigForEntity(
2625
HeuristicConfigPolicy<Solution_> configPolicy, EntityDescriptor<Solution_> entityDescriptor) {
27-
Class<?> entityClass = entityDescriptor.getEntityClass();
28-
EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig()
26+
var entityClass = entityDescriptor.getEntityClass();
27+
var entitySelectorConfig = new EntitySelectorConfig()
2928
.withId(entityClass.getName())
3029
.withEntityClass(entityClass);
3130
return deduceEntitySortManner(configPolicy, entityDescriptor, entitySelectorConfig);
@@ -44,15 +43,15 @@ public static <Solution_> EntitySelectorConfig deduceEntitySortManner(HeuristicC
4443

4544
protected EntityDescriptor<Solution_> deduceEntityDescriptor(HeuristicConfigPolicy<Solution_> configPolicy,
4645
Class<?> entityClass) {
47-
SolutionDescriptor<Solution_> solutionDescriptor = configPolicy.getSolutionDescriptor();
46+
var solutionDescriptor = configPolicy.getSolutionDescriptor();
4847
return entityClass == null
4948
? getTheOnlyEntityDescriptor(solutionDescriptor)
5049
: getEntityDescriptorForClass(solutionDescriptor, entityClass);
5150
}
5251

5352
private EntityDescriptor<Solution_> getEntityDescriptorForClass(SolutionDescriptor<Solution_> solutionDescriptor,
5453
Class<?> entityClass) {
55-
EntityDescriptor<Solution_> entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass);
54+
var entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass);
5655
if (entityDescriptor == null) {
5756
throw new IllegalArgumentException(
5857
"""
@@ -65,7 +64,7 @@ Check your solver configuration. If that class (%s) is not in the entityClassSet
6564
}
6665

6766
protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescriptor<Solution_> solutionDescriptor) {
68-
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors();
67+
var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors();
6968
if (entityDescriptors.size() != 1) {
7069
throw new IllegalArgumentException(
7170
"The config (%s) has no entityClass configured and because there are multiple in the entityClassSet (%s), it cannot be deduced automatically."
@@ -76,7 +75,7 @@ protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescrip
7675

7776
protected EntityDescriptor<Solution_>
7877
getTheOnlyEntityDescriptorWithBasicVariables(SolutionDescriptor<Solution_> solutionDescriptor) {
79-
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
78+
var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
8079
.stream()
8180
.filter(EntityDescriptor::hasAnyGenuineBasicVariables)
8281
.toList();
@@ -88,6 +87,20 @@ protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescrip
8887
return entityDescriptors.iterator().next();
8988
}
9089

90+
protected EntityDescriptor<Solution_>
91+
getTheOnlyEntityDescriptorWithListVariable(SolutionDescriptor<Solution_> solutionDescriptor) {
92+
var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
93+
.stream()
94+
.filter(EntityDescriptor::hasAnyGenuineListVariables)
95+
.toList();
96+
if (entityDescriptors.size() != 1) {
97+
throw new IllegalArgumentException(
98+
"Impossible state: the config (%s) has no entityClass configured and because there are multiple in the entityClassSet (%s), it cannot be deduced automatically."
99+
.formatted(config, solutionDescriptor.getEntityClassSet()));
100+
}
101+
return entityDescriptors.iterator().next();
102+
}
103+
91104
protected GenuineVariableDescriptor<Solution_> deduceGenuineVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor,
92105
String variableName) {
93106
return variableName == null
@@ -97,7 +110,7 @@ protected GenuineVariableDescriptor<Solution_> deduceGenuineVariableDescriptor(E
97110

98111
protected GenuineVariableDescriptor<Solution_> getVariableDescriptorForName(EntityDescriptor<Solution_> entityDescriptor,
99112
String variableName) {
100-
GenuineVariableDescriptor<Solution_> variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
113+
var variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
101114
if (variableDescriptor == null) {
102115
throw new IllegalArgumentException(
103116
"""
@@ -109,8 +122,7 @@ The config (%s) has a variableName (%s) which is not a valid planning variable o
109122
}
110123

111124
protected GenuineVariableDescriptor<Solution_> getTheOnlyVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor) {
112-
List<GenuineVariableDescriptor<Solution_>> variableDescriptorList =
113-
entityDescriptor.getGenuineVariableDescriptorList();
125+
var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList();
114126
if (variableDescriptorList.size() != 1) {
115127
throw new IllegalArgumentException(
116128
"The config (%s) has no configured variableName for entityClass (%s) and because there are multiple variableNames (%s), it cannot be deduced automatically."
@@ -123,8 +135,26 @@ protected GenuineVariableDescriptor<Solution_> getTheOnlyVariableDescriptor(Enti
123135
protected List<GenuineVariableDescriptor<Solution_>> deduceVariableDescriptorList(
124136
EntityDescriptor<Solution_> entityDescriptor, List<String> variableNameIncludeList) {
125137
Objects.requireNonNull(entityDescriptor);
126-
List<GenuineVariableDescriptor<Solution_>> variableDescriptorList =
127-
entityDescriptor.getGenuineVariableDescriptorList();
138+
var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList();
139+
if (variableNameIncludeList == null) {
140+
return variableDescriptorList;
141+
}
142+
143+
return variableNameIncludeList.stream()
144+
.map(variableNameInclude -> variableDescriptorList.stream()
145+
.filter(variableDescriptor -> variableDescriptor.getVariableName().equals(variableNameInclude))
146+
.findFirst()
147+
.orElseThrow(() -> new IllegalArgumentException(
148+
"The config (%s) has a variableNameInclude (%s) which does not exist in the entity (%s)'s variableDescriptorList (%s)."
149+
.formatted(config, variableNameInclude, entityDescriptor.getEntityClass(),
150+
variableDescriptorList))))
151+
.toList();
152+
}
153+
154+
protected List<GenuineVariableDescriptor<Solution_>> deduceBasicVariableDescriptorList(
155+
EntityDescriptor<Solution_> entityDescriptor, List<String> variableNameIncludeList) {
156+
Objects.requireNonNull(entityDescriptor);
157+
var variableDescriptorList = entityDescriptor.getGenuineBasicVariableDescriptorList();
128158
if (variableNameIncludeList == null) {
129159
return variableDescriptorList;
130160
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,12 @@ public List<GenuineVariableDescriptor<Solution_>> getGenuineVariableDescriptorLi
740740
return effectiveGenuineVariableDescriptorList;
741741
}
742742

743+
public List<GenuineVariableDescriptor<Solution_>> getGenuineBasicVariableDescriptorList() {
744+
return effectiveGenuineVariableDescriptorList.stream()
745+
.filter(descriptor -> !descriptor.isListVariable())
746+
.toList();
747+
}
748+
743749
public long getGenuineVariableCount() {
744750
return effectiveGenuineVariableDescriptorList.size();
745751
}

core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,15 @@ public PlanningSolutionMetaModel<Solution_> getMetaModel() {
894894
return planningSolutionMetaModel;
895895
}
896896

897+
public List<BasicVariableDescriptor<Solution_>> getBasicVariableDescriptorList() {
898+
return getGenuineEntityDescriptors().stream()
899+
.flatMap(entityDescriptor -> entityDescriptor.getGenuineBasicVariableDescriptorList().stream())
900+
.map(descriptor -> (BasicVariableDescriptor<Solution_>) descriptor)
901+
.toList();
902+
}
903+
897904
public boolean hasBasicVariable() {
898-
return getGenuineEntityDescriptors().stream().anyMatch(EntityDescriptor::hasAnyGenuineBasicVariables);
905+
return !getBasicVariableDescriptorList().isEmpty();
899906
}
900907

901908
public boolean hasChainedVariable() {

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/AbstractCompositeMoveSelectorFactory.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ai.timefold.solver.core.impl.heuristic.selector.move.composite;
22

33
import java.util.List;
4-
import java.util.stream.Collectors;
54

65
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
76
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
@@ -14,7 +13,7 @@
1413
abstract class AbstractCompositeMoveSelectorFactory<Solution_, MoveSelectorConfig_ extends MoveSelectorConfig<MoveSelectorConfig_>>
1514
extends AbstractMoveSelectorFactory<Solution_, MoveSelectorConfig_> {
1615

17-
public AbstractCompositeMoveSelectorFactory(MoveSelectorConfig_ moveSelectorConfig) {
16+
protected AbstractCompositeMoveSelectorFactory(MoveSelectorConfig_ moveSelectorConfig) {
1817
super(moveSelectorConfig);
1918
}
2019

@@ -24,8 +23,8 @@ protected List<MoveSelector<Solution_>> buildInnerMoveSelectors(List<MoveSelecto
2423
.map(moveSelectorConfig -> {
2524
AbstractMoveSelectorFactory<Solution_, ?> moveSelectorFactory =
2625
MoveSelectorFactory.create(moveSelectorConfig);
27-
SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection);
26+
var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection);
2827
return moveSelectorFactory.buildMoveSelector(configPolicy, minimumCacheType, selectionOrder, false);
29-
}).collect(Collectors.toList());
28+
}).toList();
3029
}
3130
}

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@
1818
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
1919
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
2020
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
21-
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
2221
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory;
2322
import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory;
2423
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
2524
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMoveSelectorFactory;
26-
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;
2725
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory;
2826

2927
import org.slf4j.Logger;
@@ -43,11 +41,11 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
4341
SelectionCacheType minimumCacheType, boolean randomSelection) {
4442
checkUnfolded("entitySelectorConfig", config.getEntitySelectorConfig());
4543
checkUnfolded("valueSelectorConfig", config.getValueSelectorConfig());
46-
SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection);
47-
EntitySelector<Solution_> entitySelector = EntitySelectorFactory
44+
var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection);
45+
var entitySelector = EntitySelectorFactory
4846
.<Solution_> create(config.getEntitySelectorConfig())
4947
.buildEntitySelector(configPolicy, minimumCacheType, selectionOrder);
50-
ValueSelector<Solution_> valueSelector = ValueSelectorFactory
48+
var valueSelector = ValueSelectorFactory
5149
.<Solution_> create(config.getValueSelectorConfig())
5250
.buildValueSelector(configPolicy, entitySelector.getEntityDescriptor(), minimumCacheType, selectionOrder);
5351
return new ChangeMoveSelector<>(entitySelector, valueSelector, randomSelection);
@@ -56,17 +54,17 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
5654
@Override
5755
protected MoveSelectorConfig<?> buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy<Solution_> configPolicy) {
5856
Collection<EntityDescriptor<Solution_>> entityDescriptors;
59-
EntityDescriptor<Solution_> onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null
57+
var onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null
6058
: EntitySelectorFactory.<Solution_> create(config.getEntitySelectorConfig())
6159
.extractEntityDescriptor(configPolicy);
6260
if (onlyEntityDescriptor != null) {
6361
entityDescriptors = Collections.singletonList(onlyEntityDescriptor);
6462
} else {
6563
entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors();
6664
}
67-
List<GenuineVariableDescriptor<Solution_>> variableDescriptorList = new ArrayList<>();
65+
var variableDescriptorList = new ArrayList<GenuineVariableDescriptor<Solution_>>();
6866
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptors) {
69-
GenuineVariableDescriptor<Solution_> onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null
67+
var onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null
7068
: ValueSelectorFactory.<Solution_> create(config.getValueSelectorConfig())
7169
.extractVariableDescriptor(configPolicy, entityDescriptor);
7270
if (onlyVariableDescriptor != null) {
@@ -82,28 +80,35 @@ protected MoveSelectorConfig<?> buildUnfoldedMoveSelectorConfig(HeuristicConfigP
8280
variableDescriptorList.addAll(entityDescriptor.getGenuineVariableDescriptorList());
8381
}
8482
}
85-
return buildUnfoldedMoveSelectorConfig(variableDescriptorList);
83+
return buildUnfoldedMoveSelectorConfig(configPolicy, variableDescriptorList);
8684
}
8785

88-
protected MoveSelectorConfig<?> buildUnfoldedMoveSelectorConfig(
86+
protected MoveSelectorConfig<?> buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy<Solution_> configPolicy,
8987
List<GenuineVariableDescriptor<Solution_>> variableDescriptorList) {
90-
List<MoveSelectorConfig> moveSelectorConfigList = new ArrayList<>(variableDescriptorList.size());
91-
for (GenuineVariableDescriptor<Solution_> variableDescriptor : variableDescriptorList) {
88+
var moveSelectorConfigList = new ArrayList<MoveSelectorConfig>(variableDescriptorList.size());
89+
for (var variableDescriptor : variableDescriptorList) {
9290
if (variableDescriptor.isListVariable()) {
91+
if (configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()) {
92+
// When using a mixed model,
93+
// we do not create a list move
94+
// and delegate it to the ListChangeMoveSelectorFactory.
95+
// The strategy aims to provide a more normalized move selector collection for mixed models.
96+
continue;
97+
}
9398
// No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded()
94-
ListChangeMoveSelectorConfig childMoveSelectorConfig =
99+
var childMoveSelectorConfig =
95100
buildListChangeMoveSelectorConfig((ListVariableDescriptor<?>) variableDescriptor, false);
96101
moveSelectorConfigList.add(childMoveSelectorConfig);
97102
} else {
98103
// No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded()
99-
ChangeMoveSelectorConfig childMoveSelectorConfig = new ChangeMoveSelectorConfig();
104+
var childMoveSelectorConfig = new ChangeMoveSelectorConfig();
100105
// Different EntitySelector per child because it is a union
101-
EntitySelectorConfig childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig());
106+
var childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig());
102107
if (childEntitySelectorConfig.getMimicSelectorRef() == null) {
103108
childEntitySelectorConfig.setEntityClass(variableDescriptor.getEntityDescriptor().getEntityClass());
104109
}
105110
childMoveSelectorConfig.setEntitySelectorConfig(childEntitySelectorConfig);
106-
ValueSelectorConfig childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig());
111+
var childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig());
107112
if (childValueSelectorConfig.getMimicSelectorRef() == null) {
108113
childValueSelectorConfig.setVariableName(variableDescriptor.getVariableName());
109114
}
@@ -130,8 +135,8 @@ The changeMoveSelectorConfig ({}) is being used for a list variable.
130135
We are keeping this option through the 1.x release stream for backward compatibility reasons.
131136
Please update your solver config to use {} now.""",
132137
config, ListChangeMoveSelectorConfig.class.getSimpleName());
133-
ListChangeMoveSelectorConfig listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig(
134-
variableDescriptor, config.getValueSelectorConfig(), createDestinationSelectorConfig());
138+
var listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig(variableDescriptor,
139+
config.getValueSelectorConfig(), createDestinationSelectorConfig());
135140
if (inheritFoldedConfig) {
136141
listChangeMoveSelectorConfig.inheritFolded(config);
137142
}

0 commit comments

Comments
 (0)