Skip to content

Commit 999d2bf

Browse files
authored
feat: enable Basic var and List var to coexist for the local search (#1606)
This PR completes the feature and enables the mixed model for local search.
1 parent 3e8eeef commit 999d2bf

File tree

87 files changed

+2081
-2203
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+2081
-2203
lines changed

benchmark/src/main/resources/benchmark.xsd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@
674674
<xs:element minOccurs="0" name="valueSorterManner" type="tns:valueSorterManner"/>
675675

676676

677-
<xs:choice maxOccurs="unbounded" minOccurs="0">
677+
<xs:choice minOccurs="0">
678678

679679

680680
<xs:element name="queuedEntityPlacer" type="tns:queuedEntityPlacerConfig"/>

core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicPhaseConfig.java

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ai.timefold.solver.core.config.constructionheuristic;
22

3-
import java.util.Arrays;
43
import java.util.List;
54
import java.util.function.Consumer;
65

@@ -37,7 +36,7 @@
3736
"constructionHeuristicType",
3837
"entitySorterManner",
3938
"valueSorterManner",
40-
"entityPlacerConfigList",
39+
"entityPlacerConfig",
4140
"moveSelectorConfigList",
4241
"foragerConfig"
4342
})
@@ -57,9 +56,9 @@ public class ConstructionHeuristicPhaseConfig extends PhaseConfig<ConstructionHe
5756
@XmlElement(name = "queuedValuePlacer", type = QueuedValuePlacerConfig.class),
5857
@XmlElement(name = "pooledEntityPlacer", type = PooledEntityPlacerConfig.class)
5958
})
60-
protected List<EntityPlacerConfig> entityPlacerConfigList = null;
59+
protected EntityPlacerConfig entityPlacerConfig = null;
6160

62-
/** Simpler alternative for {@link #entityPlacerConfigList}. */
61+
/** Simpler alternative for {@link #entityPlacerConfig}. */
6362
@XmlElements({
6463
@XmlElement(name = CartesianProductMoveSelectorConfig.XML_ELEMENT_NAME,
6564
type = CartesianProductMoveSelectorConfig.class),
@@ -111,35 +110,12 @@ public void setValueSorterManner(@Nullable ValueSorterManner valueSorterManner)
111110
this.valueSorterManner = valueSorterManner;
112111
}
113112

114-
public List<EntityPlacerConfig> getEntityPlacerConfigList() {
115-
return entityPlacerConfigList;
116-
}
117-
118-
public void setEntityPlacerConfigList(List<EntityPlacerConfig> entityPlacerConfigList) {
119-
this.entityPlacerConfigList = entityPlacerConfigList;
113+
public @Nullable EntityPlacerConfig getEntityPlacerConfig() {
114+
return entityPlacerConfig;
120115
}
121116

122-
/**
123-
* @deprecated Use {@link #setEntityPlacerConfigList(List)}} instead.
124-
*/
125-
@Deprecated(forRemoval = true, since = "1.22.0")
126-
public void setEntityPlacerConfig(EntityPlacerConfig entityPlacerConfig) {
127-
setEntityPlacerConfigList(List.of(entityPlacerConfig));
128-
}
129-
130-
/**
131-
* @deprecated Use {@link #getEntityPlacerConfigList()} instead.
132-
*/
133-
@Deprecated(forRemoval = true, since = "1.22.0")
134-
public @Nullable EntityPlacerConfig getEntityPlacerConfig() {
135-
if (entityPlacerConfigList == null || entityPlacerConfigList.isEmpty()) {
136-
return null;
137-
}
138-
if (entityPlacerConfigList.size() > 1) {
139-
throw new IllegalStateException(
140-
"Returning a single entity placer configuration is not possible. Maybe use getEntityPlacerConfigList instead.");
141-
}
142-
return entityPlacerConfigList.get(0);
117+
public void setEntityPlacerConfig(@Nullable EntityPlacerConfig entityPlacerConfig) {
118+
this.entityPlacerConfig = entityPlacerConfig;
143119
}
144120

145121
public @Nullable List<@NonNull MoveSelectorConfig> getMoveSelectorConfigList() {
@@ -178,19 +154,8 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
178154
return this;
179155
}
180156

181-
public @NonNull ConstructionHeuristicPhaseConfig
182-
withEntityPlacerConfigList(@NonNull EntityPlacerConfig<?>... entityPlacerConfig) {
183-
setEntityPlacerConfigList(Arrays.asList(entityPlacerConfig));
184-
return this;
185-
}
186-
187-
/**
188-
* @deprecated use {@link #withEntityPlacerConfigList(EntityPlacerConfig[])} instead.
189-
*/
190-
@Deprecated(forRemoval = true, since = "1.22.0")
191-
public @NonNull ConstructionHeuristicPhaseConfig
192-
withEntityPlacerConfig(@NonNull EntityPlacerConfig entityPlacerConfig) {
193-
setEntityPlacerConfigList(List.of(entityPlacerConfig));
157+
public @NonNull ConstructionHeuristicPhaseConfig withEntityPlacerConfig(@NonNull EntityPlacerConfig<?> entityPlacerConfig) {
158+
this.entityPlacerConfig = entityPlacerConfig;
194159
return this;
195160
}
196161

@@ -215,8 +180,8 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
215180
inheritedConfig.getEntitySorterManner());
216181
valueSorterManner = ConfigUtils.inheritOverwritableProperty(valueSorterManner,
217182
inheritedConfig.getValueSorterManner());
218-
entityPlacerConfigList = ConfigUtils.inheritMergeableListConfig(
219-
entityPlacerConfigList, inheritedConfig.getEntityPlacerConfigList());
183+
setEntityPlacerConfig(ConfigUtils.inheritOverwritableProperty(
184+
getEntityPlacerConfig(), inheritedConfig.getEntityPlacerConfig()));
220185
moveSelectorConfigList = ConfigUtils.inheritMergeableListConfig(
221186
moveSelectorConfigList, inheritedConfig.getMoveSelectorConfigList());
222187
foragerConfig = ConfigUtils.inheritConfig(foragerConfig, inheritedConfig.getForagerConfig());
@@ -233,8 +198,8 @@ public void visitReferencedClasses(@NonNull Consumer<Class<?>> classVisitor) {
233198
if (terminationConfig != null) {
234199
terminationConfig.visitReferencedClasses(classVisitor);
235200
}
236-
if (entityPlacerConfigList != null) {
237-
entityPlacerConfigList.forEach(entityPlacerConfig -> entityPlacerConfig.visitReferencedClasses(classVisitor));
201+
if (entityPlacerConfig != null) {
202+
entityPlacerConfig.visitReferencedClasses(classVisitor);
238203
}
239204
if (moveSelectorConfigList != null) {
240205
moveSelectorConfigList.forEach(ms -> ms.visitReferencedClasses(classVisitor));

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/constructionheuristic/DefaultConstructionHeuristicPhase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void solve(SolverScope<Solution_> solverScope) {
5757
phaseStarted(phaseScope);
5858

5959
var solutionDescriptor = solverScope.getSolutionDescriptor();
60-
var hasListVariable = solutionDescriptor.hasListVariable();
60+
var hasListVariable = moveRepository.hasListVariable();
6161
var maxStepCount = -1;
6262
if (hasListVariable) {
6363
// In case of list variable with support for unassigned values, the placer will iterate indefinitely.

0 commit comments

Comments
 (0)