Skip to content

Commit 1c94981

Browse files
committed
feat: improve the CH strategy
1 parent d2ea238 commit 1c94981

18 files changed

Lines changed: 262 additions & 1114 deletions

File tree

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/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.

core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java

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

3-
import java.util.ArrayList;
43
import java.util.Objects;
54
import java.util.Optional;
65

@@ -29,7 +28,6 @@
2928
import ai.timefold.solver.core.impl.constructionheuristic.placer.PooledEntityPlacerFactory;
3029
import ai.timefold.solver.core.impl.constructionheuristic.placer.QueuedEntityPlacerFactory;
3130
import ai.timefold.solver.core.impl.constructionheuristic.placer.QueuedValuePlacerFactory;
32-
import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig;
3331
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
3432
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
3533
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
@@ -87,38 +85,10 @@ public ConstructionHeuristicPhase<Solution_> buildPhase(int phaseIndex, boolean
8785
}
8886

8987
private Optional<EntityPlacerConfig<?>> getValidEntityPlacerConfig() {
90-
if (phaseConfig.getEntityPlacerConfigList() == null || phaseConfig.getEntityPlacerConfigList().isEmpty()) {
88+
var entityPlacerConfig = phaseConfig.getEntityPlacerConfig();
89+
if (entityPlacerConfig == null) {
9190
return Optional.empty();
9291
}
93-
94-
if (phaseConfig.getEntityPlacerConfigList().size() > 2) {
95-
throw new IllegalArgumentException(
96-
"The Construction Heuristic configuration (%s) only support a maximum of two entity placers."
97-
.formatted(phaseConfig));
98-
}
99-
if (phaseConfig.getEntityPlacerConfigList().stream().anyMatch(PooledEntityPlacerConfig.class::isInstance)
100-
&& phaseConfig.getEntityPlacerConfigList().size() == 2) {
101-
throw new IllegalArgumentException(
102-
"The Construction Heuristic configuration (%s) does not support multiple configurations when using the pooled placer configuration %s."
103-
.formatted(phaseConfig, PooledEntityPlacerConfig.class.getSimpleName()));
104-
}
105-
if (phaseConfig.getEntityPlacerConfigList().stream().map(EntityPlacerConfig::getClass).distinct().count() == 1
106-
&& phaseConfig.getEntityPlacerConfigList().size() == 2) {
107-
var message = "The Construction Heuristic configuration (%s) cannot contain duplicate placer configurations."
108-
.formatted(phaseConfig);
109-
if (phaseConfig.getEntityPlacerConfigList().get(0) instanceof QueuedEntityPlacerConfig) {
110-
throw new IllegalArgumentException("""
111-
%s
112-
Maybe define multiple move selectors if there are more than one basic variables.""".formatted(message));
113-
}
114-
throw new IllegalArgumentException(message);
115-
}
116-
117-
var entityPlacerConfig = phaseConfig.getEntityPlacerConfigList().get(0);
118-
if (phaseConfig.getEntityPlacerConfigList().size() == 2) {
119-
entityPlacerConfig = new QueuedMultiplePlacerConfig()
120-
.withPlacerConfigList(phaseConfig.getEntityPlacerConfigList());
121-
}
12292
if (phaseConfig.getConstructionHeuristicType() != null) {
12393
throw new IllegalArgumentException(
12494
"The constructionHeuristicType (%s) must not be configured if the entityPlacerConfig (%s) is explicitly configured."
@@ -130,32 +100,14 @@ private Optional<EntityPlacerConfig<?>> getValidEntityPlacerConfig() {
130100
"The moveSelectorConfigList (%s) cannot be configured if the entityPlacerConfig (%s) is explicitly configured."
131101
.formatted(moveSelectorConfigList, entityPlacerConfig));
132102
}
133-
134103
return Optional.of(entityPlacerConfig);
135104
}
136105

137-
@SuppressWarnings("rawtypes")
138106
private EntityPlacerConfig<?> buildDefaultEntityPlacerConfig(HeuristicConfigPolicy<Solution_> configPolicy,
139107
ConstructionHeuristicType constructionHeuristicType) {
140-
var listVariableDescriptor = findValidListVariableDescriptor(configPolicy.getSolutionDescriptor()).orElse(null);
141-
if (configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()) {
142-
if (listVariableDescriptor == null) {
143-
throw new IllegalStateException("Impossible state: the list variable descriptor is null.");
144-
}
145-
var placerConfigList = new ArrayList<EntityPlacerConfig>();
146-
// Generate the default configuration for the list variable
147-
placerConfigList.add(buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor));
148-
// Generate a single config for the basic variable(s)
149-
// When multiple basic variables are defined, a Cartesian product is created
150-
placerConfigList.add(buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType));
151-
return new QueuedMultiplePlacerConfig().withPlacerConfigList(placerConfigList);
152-
} else {
153-
if (listVariableDescriptor != null) {
154-
return buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor);
155-
} else {
156-
return buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType);
157-
}
158-
}
108+
return findValidListVariableDescriptor(configPolicy.getSolutionDescriptor())
109+
.map(listVariableDescriptor -> buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor))
110+
.orElseGet(() -> buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType));
159111
}
160112

161113
private Optional<ListVariableDescriptor<?>>
@@ -166,6 +118,15 @@ private EntityPlacerConfig<?> buildDefaultEntityPlacerConfig(HeuristicConfigPoli
166118
}
167119
failIfConfigured(phaseConfig.getConstructionHeuristicType(), "constructionHeuristicType");
168120
failIfConfigured(phaseConfig.getMoveSelectorConfigList(), "moveSelectorConfigList");
121+
// When an entity has both list and basic variables,
122+
// the CH configuration will require two separate placers to initialize each variable,
123+
// which cannot be deduced automatically by default, since a single placer would be returned
124+
if (listVariableDescriptor.getEntityDescriptor().hasAnyGenuineBasicVariables()) {
125+
throw new IllegalArgumentException("""
126+
The entity (%s) has both basic and list variables and cannot be deduced automatically.
127+
Maybe customize the phase configuration and add separate construction heuristic phases for each variable."""
128+
.formatted(listVariableDescriptor.getEntityDescriptor().getEntityClass()));
129+
}
169130
return Optional.of(listVariableDescriptor);
170131
}
171132

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import ai.timefold.solver.core.config.constructionheuristic.placer.PooledEntityPlacerConfig;
55
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig;
66
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig;
7-
import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig;
87
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
98

109
public interface EntityPlacerFactory<Solution_> {
@@ -17,8 +16,6 @@ static <Solution_> EntityPlacerFactory<Solution_> create(EntityPlacerConfig<?> e
1716
return new QueuedEntityPlacerFactory<>(queuedEntityPlacerConfig);
1817
} else if (entityPlacerConfig instanceof QueuedValuePlacerConfig queuedValuePlacerConfig) {
1918
return new QueuedValuePlacerFactory<>(queuedValuePlacerConfig);
20-
} else if (entityPlacerConfig instanceof QueuedMultiplePlacerConfig queuedMultiplePlacerConfig) {
21-
return new QueuedMultiplePlacerFactory<>(queuedMultiplePlacerConfig);
2219
} else {
2320
throw new IllegalArgumentException(String.format("Unknown %s type: (%s).",
2421
EntityPlacerConfig.class.getSimpleName(), entityPlacerConfig.getClass().getName()));

0 commit comments

Comments
 (0)