diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd
index 7fb34bd4308..6bb3594d055 100644
--- a/benchmark/src/main/resources/benchmark.xsd
+++ b/benchmark/src/main/resources/benchmark.xsd
@@ -674,7 +674,7 @@
-
+
diff --git a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicPhaseConfig.java b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicPhaseConfig.java
index fca788048fb..e8a46a4d9b5 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicPhaseConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicPhaseConfig.java
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.config.constructionheuristic;
-import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -37,7 +36,7 @@
"constructionHeuristicType",
"entitySorterManner",
"valueSorterManner",
- "entityPlacerConfigList",
+ "entityPlacerConfig",
"moveSelectorConfigList",
"foragerConfig"
})
@@ -57,9 +56,9 @@ public class ConstructionHeuristicPhaseConfig extends PhaseConfig entityPlacerConfigList = null;
+ protected EntityPlacerConfig entityPlacerConfig = null;
- /** Simpler alternative for {@link #entityPlacerConfigList}. */
+ /** Simpler alternative for {@link #entityPlacerConfig}. */
@XmlElements({
@XmlElement(name = CartesianProductMoveSelectorConfig.XML_ELEMENT_NAME,
type = CartesianProductMoveSelectorConfig.class),
@@ -111,35 +110,12 @@ public void setValueSorterManner(@Nullable ValueSorterManner valueSorterManner)
this.valueSorterManner = valueSorterManner;
}
- public List getEntityPlacerConfigList() {
- return entityPlacerConfigList;
- }
-
- public void setEntityPlacerConfigList(List entityPlacerConfigList) {
- this.entityPlacerConfigList = entityPlacerConfigList;
+ public @Nullable EntityPlacerConfig getEntityPlacerConfig() {
+ return entityPlacerConfig;
}
- /**
- * @deprecated Use {@link #setEntityPlacerConfigList(List)}} instead.
- */
- @Deprecated(forRemoval = true, since = "1.22.0")
- public void setEntityPlacerConfig(EntityPlacerConfig entityPlacerConfig) {
- setEntityPlacerConfigList(List.of(entityPlacerConfig));
- }
-
- /**
- * @deprecated Use {@link #getEntityPlacerConfigList()} instead.
- */
- @Deprecated(forRemoval = true, since = "1.22.0")
- public @Nullable EntityPlacerConfig getEntityPlacerConfig() {
- if (entityPlacerConfigList == null || entityPlacerConfigList.isEmpty()) {
- return null;
- }
- if (entityPlacerConfigList.size() > 1) {
- throw new IllegalStateException(
- "Returning a single entity placer configuration is not possible. Maybe use getEntityPlacerConfigList instead.");
- }
- return entityPlacerConfigList.get(0);
+ public void setEntityPlacerConfig(@Nullable EntityPlacerConfig entityPlacerConfig) {
+ this.entityPlacerConfig = entityPlacerConfig;
}
public @Nullable List<@NonNull MoveSelectorConfig> getMoveSelectorConfigList() {
@@ -178,19 +154,8 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
return this;
}
- public @NonNull ConstructionHeuristicPhaseConfig
- withEntityPlacerConfigList(@NonNull EntityPlacerConfig>... entityPlacerConfig) {
- setEntityPlacerConfigList(Arrays.asList(entityPlacerConfig));
- return this;
- }
-
- /**
- * @deprecated use {@link #withEntityPlacerConfigList(EntityPlacerConfig[])} instead.
- */
- @Deprecated(forRemoval = true, since = "1.22.0")
- public @NonNull ConstructionHeuristicPhaseConfig
- withEntityPlacerConfig(@NonNull EntityPlacerConfig entityPlacerConfig) {
- setEntityPlacerConfigList(List.of(entityPlacerConfig));
+ public @NonNull ConstructionHeuristicPhaseConfig withEntityPlacerConfig(@NonNull EntityPlacerConfig> entityPlacerConfig) {
+ this.entityPlacerConfig = entityPlacerConfig;
return this;
}
@@ -215,8 +180,8 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
inheritedConfig.getEntitySorterManner());
valueSorterManner = ConfigUtils.inheritOverwritableProperty(valueSorterManner,
inheritedConfig.getValueSorterManner());
- entityPlacerConfigList = ConfigUtils.inheritMergeableListConfig(
- entityPlacerConfigList, inheritedConfig.getEntityPlacerConfigList());
+ setEntityPlacerConfig(ConfigUtils.inheritOverwritableProperty(
+ getEntityPlacerConfig(), inheritedConfig.getEntityPlacerConfig()));
moveSelectorConfigList = ConfigUtils.inheritMergeableListConfig(
moveSelectorConfigList, inheritedConfig.getMoveSelectorConfigList());
foragerConfig = ConfigUtils.inheritConfig(foragerConfig, inheritedConfig.getForagerConfig());
@@ -233,8 +198,8 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) {
if (terminationConfig != null) {
terminationConfig.visitReferencedClasses(classVisitor);
}
- if (entityPlacerConfigList != null) {
- entityPlacerConfigList.forEach(entityPlacerConfig -> entityPlacerConfig.visitReferencedClasses(classVisitor));
+ if (entityPlacerConfig != null) {
+ entityPlacerConfig.visitReferencedClasses(classVisitor);
}
if (moveSelectorConfigList != null) {
moveSelectorConfigList.forEach(ms -> ms.visitReferencedClasses(classVisitor));
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java
index ff691c260a8..c70a1bcac6c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.impl;
-import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -24,8 +23,8 @@ protected AbstractFromConfigFactory(Config_ config) {
public static EntitySelectorConfig getDefaultEntitySelectorConfigForEntity(
HeuristicConfigPolicy configPolicy, EntityDescriptor entityDescriptor) {
- Class> entityClass = entityDescriptor.getEntityClass();
- EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig()
+ var entityClass = entityDescriptor.getEntityClass();
+ var entitySelectorConfig = new EntitySelectorConfig()
.withId(entityClass.getName())
.withEntityClass(entityClass);
return deduceEntitySortManner(configPolicy, entityDescriptor, entitySelectorConfig);
@@ -44,7 +43,7 @@ public static EntitySelectorConfig deduceEntitySortManner(HeuristicC
protected EntityDescriptor deduceEntityDescriptor(HeuristicConfigPolicy configPolicy,
Class> entityClass) {
- SolutionDescriptor solutionDescriptor = configPolicy.getSolutionDescriptor();
+ var solutionDescriptor = configPolicy.getSolutionDescriptor();
return entityClass == null
? getTheOnlyEntityDescriptor(solutionDescriptor)
: getEntityDescriptorForClass(solutionDescriptor, entityClass);
@@ -52,7 +51,7 @@ protected EntityDescriptor deduceEntityDescriptor(HeuristicConfigPoli
private EntityDescriptor getEntityDescriptorForClass(SolutionDescriptor solutionDescriptor,
Class> entityClass) {
- EntityDescriptor entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass);
+ var entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass);
if (entityDescriptor == null) {
throw new IllegalArgumentException(
"""
@@ -65,7 +64,7 @@ Check your solver configuration. If that class (%s) is not in the entityClassSet
}
protected EntityDescriptor getTheOnlyEntityDescriptor(SolutionDescriptor solutionDescriptor) {
- Collection> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors();
+ var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors();
if (entityDescriptors.size() != 1) {
throw new IllegalArgumentException(
"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 getTheOnlyEntityDescriptor(SolutionDescrip
protected EntityDescriptor
getTheOnlyEntityDescriptorWithBasicVariables(SolutionDescriptor solutionDescriptor) {
- Collection> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
+ var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
.stream()
.filter(EntityDescriptor::hasAnyGenuineBasicVariables)
.toList();
@@ -88,6 +87,20 @@ protected EntityDescriptor getTheOnlyEntityDescriptor(SolutionDescrip
return entityDescriptors.iterator().next();
}
+ protected EntityDescriptor
+ getTheOnlyEntityDescriptorWithListVariable(SolutionDescriptor solutionDescriptor) {
+ var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
+ .stream()
+ .filter(EntityDescriptor::hasAnyGenuineListVariables)
+ .toList();
+ if (entityDescriptors.size() != 1) {
+ throw new IllegalArgumentException(
+ "Impossible state: the config (%s) has no entityClass configured and because there are multiple in the entityClassSet (%s), it cannot be deduced automatically."
+ .formatted(config, solutionDescriptor.getEntityClassSet()));
+ }
+ return entityDescriptors.iterator().next();
+ }
+
protected GenuineVariableDescriptor deduceGenuineVariableDescriptor(EntityDescriptor entityDescriptor,
String variableName) {
return variableName == null
@@ -97,7 +110,7 @@ protected GenuineVariableDescriptor deduceGenuineVariableDescriptor(E
protected GenuineVariableDescriptor getVariableDescriptorForName(EntityDescriptor entityDescriptor,
String variableName) {
- GenuineVariableDescriptor variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
+ var variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
if (variableDescriptor == null) {
throw new IllegalArgumentException(
"""
@@ -109,8 +122,7 @@ The config (%s) has a variableName (%s) which is not a valid planning variable o
}
protected GenuineVariableDescriptor getTheOnlyVariableDescriptor(EntityDescriptor entityDescriptor) {
- List> variableDescriptorList =
- entityDescriptor.getGenuineVariableDescriptorList();
+ var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList();
if (variableDescriptorList.size() != 1) {
throw new IllegalArgumentException(
"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 getTheOnlyVariableDescriptor(Enti
protected List> deduceVariableDescriptorList(
EntityDescriptor entityDescriptor, List variableNameIncludeList) {
Objects.requireNonNull(entityDescriptor);
- List> variableDescriptorList =
- entityDescriptor.getGenuineVariableDescriptorList();
+ var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList();
+ if (variableNameIncludeList == null) {
+ return variableDescriptorList;
+ }
+
+ return variableNameIncludeList.stream()
+ .map(variableNameInclude -> variableDescriptorList.stream()
+ .filter(variableDescriptor -> variableDescriptor.getVariableName().equals(variableNameInclude))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "The config (%s) has a variableNameInclude (%s) which does not exist in the entity (%s)'s variableDescriptorList (%s)."
+ .formatted(config, variableNameInclude, entityDescriptor.getEntityClass(),
+ variableDescriptorList))))
+ .toList();
+ }
+
+ protected List> deduceBasicVariableDescriptorList(
+ EntityDescriptor entityDescriptor, List variableNameIncludeList) {
+ Objects.requireNonNull(entityDescriptor);
+ var variableDescriptorList = entityDescriptor.getGenuineBasicVariableDescriptorList();
if (variableNameIncludeList == null) {
return variableDescriptorList;
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java
index e16e8477b9c..92174b423db 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java
@@ -57,7 +57,7 @@ public void solve(SolverScope solverScope) {
phaseStarted(phaseScope);
var solutionDescriptor = solverScope.getSolutionDescriptor();
- var hasListVariable = solutionDescriptor.hasListVariable();
+ var hasListVariable = moveRepository.hasListVariable();
var maxStepCount = -1;
if (hasListVariable) {
// In case of list variable with support for unassigned values, the placer will iterate indefinitely.
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java
index 9f561892594..f14e5137067 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.impl.constructionheuristic;
-import java.util.ArrayList;
import java.util.Objects;
import java.util.Optional;
@@ -29,7 +28,6 @@
import ai.timefold.solver.core.impl.constructionheuristic.placer.PooledEntityPlacerFactory;
import ai.timefold.solver.core.impl.constructionheuristic.placer.QueuedEntityPlacerFactory;
import ai.timefold.solver.core.impl.constructionheuristic.placer.QueuedValuePlacerFactory;
-import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
@@ -87,38 +85,10 @@ public ConstructionHeuristicPhase buildPhase(int phaseIndex, boolean
}
private Optional> getValidEntityPlacerConfig() {
- if (phaseConfig.getEntityPlacerConfigList() == null || phaseConfig.getEntityPlacerConfigList().isEmpty()) {
+ var entityPlacerConfig = phaseConfig.getEntityPlacerConfig();
+ if (entityPlacerConfig == null) {
return Optional.empty();
}
-
- if (phaseConfig.getEntityPlacerConfigList().size() > 2) {
- throw new IllegalArgumentException(
- "The Construction Heuristic configuration (%s) only support a maximum of two entity placers."
- .formatted(phaseConfig));
- }
- if (phaseConfig.getEntityPlacerConfigList().stream().anyMatch(PooledEntityPlacerConfig.class::isInstance)
- && phaseConfig.getEntityPlacerConfigList().size() == 2) {
- throw new IllegalArgumentException(
- "The Construction Heuristic configuration (%s) does not support multiple configurations when using the pooled placer configuration %s."
- .formatted(phaseConfig, PooledEntityPlacerConfig.class.getSimpleName()));
- }
- if (phaseConfig.getEntityPlacerConfigList().stream().map(EntityPlacerConfig::getClass).distinct().count() == 1
- && phaseConfig.getEntityPlacerConfigList().size() == 2) {
- var message = "The Construction Heuristic configuration (%s) cannot contain duplicate placer configurations."
- .formatted(phaseConfig);
- if (phaseConfig.getEntityPlacerConfigList().get(0) instanceof QueuedEntityPlacerConfig) {
- throw new IllegalArgumentException("""
- %s
- Maybe define multiple move selectors if there are more than one basic variables.""".formatted(message));
- }
- throw new IllegalArgumentException(message);
- }
-
- var entityPlacerConfig = phaseConfig.getEntityPlacerConfigList().get(0);
- if (phaseConfig.getEntityPlacerConfigList().size() == 2) {
- entityPlacerConfig = new QueuedMultiplePlacerConfig()
- .withPlacerConfigList(phaseConfig.getEntityPlacerConfigList());
- }
if (phaseConfig.getConstructionHeuristicType() != null) {
throw new IllegalArgumentException(
"The constructionHeuristicType (%s) must not be configured if the entityPlacerConfig (%s) is explicitly configured."
@@ -130,32 +100,14 @@ private Optional> getValidEntityPlacerConfig() {
"The moveSelectorConfigList (%s) cannot be configured if the entityPlacerConfig (%s) is explicitly configured."
.formatted(moveSelectorConfigList, entityPlacerConfig));
}
-
return Optional.of(entityPlacerConfig);
}
- @SuppressWarnings("rawtypes")
private EntityPlacerConfig> buildDefaultEntityPlacerConfig(HeuristicConfigPolicy configPolicy,
ConstructionHeuristicType constructionHeuristicType) {
- var listVariableDescriptor = findValidListVariableDescriptor(configPolicy.getSolutionDescriptor()).orElse(null);
- if (configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()) {
- if (listVariableDescriptor == null) {
- throw new IllegalStateException("Impossible state: the list variable descriptor is null.");
- }
- var placerConfigList = new ArrayList();
- // Generate the default configuration for the list variable
- placerConfigList.add(buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor));
- // Generate a single config for the basic variable(s)
- // When multiple basic variables are defined, a Cartesian product is created
- placerConfigList.add(buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType));
- return new QueuedMultiplePlacerConfig().withPlacerConfigList(placerConfigList);
- } else {
- if (listVariableDescriptor != null) {
- return buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor);
- } else {
- return buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType);
- }
- }
+ return findValidListVariableDescriptor(configPolicy.getSolutionDescriptor())
+ .map(listVariableDescriptor -> buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor))
+ .orElseGet(() -> buildUnfoldedEntityPlacerConfig(configPolicy, constructionHeuristicType));
}
private Optional>
@@ -166,6 +118,15 @@ private EntityPlacerConfig> buildDefaultEntityPlacerConfig(HeuristicConfigPoli
}
failIfConfigured(phaseConfig.getConstructionHeuristicType(), "constructionHeuristicType");
failIfConfigured(phaseConfig.getMoveSelectorConfigList(), "moveSelectorConfigList");
+ // When an entity has both list and basic variables,
+ // the CH configuration will require two separate placers to initialize each variable,
+ // which cannot be deduced automatically by default, since a single placer would be returned
+ if (listVariableDescriptor.getEntityDescriptor().hasAnyGenuineBasicVariables()) {
+ throw new IllegalArgumentException("""
+ The entity (%s) has both basic and list variables and cannot be deduced automatically.
+ Maybe customize the phase configuration and add separate construction heuristic phases for each variable."""
+ .formatted(listVariableDescriptor.getEntityDescriptor().getEntityClass()));
+ }
return Optional.of(listVariableDescriptor);
}
@@ -201,6 +162,7 @@ public static EntityPlacerConfig buildListVariableQueuedValuePlacerConfig(Heuris
// Finally, QueuedValuePlacer uses the recording ValueSelector and a ListChangeMoveSelector.
// The ListChangeMoveSelector's replaying ValueSelector mimics the QueuedValuePlacer's recording ValueSelector.
return new QueuedValuePlacerConfig()
+ .withEntityClass(variableDescriptor.getEntityDescriptor().getEntityClass())
.withValueSelectorConfig(mimicRecordingValueSelectorConfig)
.withMoveSelectorConfig(listChangeMoveSelectorConfig);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/EntityPlacerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/EntityPlacerFactory.java
index 97837552cd9..7183ecfb18f 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/EntityPlacerFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/EntityPlacerFactory.java
@@ -4,7 +4,6 @@
import ai.timefold.solver.core.config.constructionheuristic.placer.PooledEntityPlacerConfig;
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig;
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig;
-import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
public interface EntityPlacerFactory {
@@ -17,8 +16,6 @@ static EntityPlacerFactory create(EntityPlacerConfig> e
return new QueuedEntityPlacerFactory<>(queuedEntityPlacerConfig);
} else if (entityPlacerConfig instanceof QueuedValuePlacerConfig queuedValuePlacerConfig) {
return new QueuedValuePlacerFactory<>(queuedValuePlacerConfig);
- } else if (entityPlacerConfig instanceof QueuedMultiplePlacerConfig queuedMultiplePlacerConfig) {
- return new QueuedMultiplePlacerFactory<>(queuedMultiplePlacerConfig);
} else {
throw new IllegalArgumentException(String.format("Unknown %s type: (%s).",
EntityPlacerConfig.class.getSimpleName(), entityPlacerConfig.getClass().getName()));
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacer.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacer.java
deleted file mode 100644
index 8c051816b1a..00000000000
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacer.java
+++ /dev/null
@@ -1,249 +0,0 @@
-package ai.timefold.solver.core.impl.constructionheuristic.placer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
-import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
-import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
-import ai.timefold.solver.core.impl.heuristic.selector.move.composite.CartesianProductMoveSelector;
-import ai.timefold.solver.core.impl.move.generic.CompositeMove;
-import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListener;
-import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
-import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope;
-import ai.timefold.solver.core.impl.solver.scope.SolverScope;
-import ai.timefold.solver.core.preview.api.move.Move;
-
-public class QueuedMultiplePlacer extends AbstractEntityPlacer
- implements EntityPlacer {
-
- private final List> queuedPlacerList;
-
- public QueuedMultiplePlacer(EntityPlacerFactory factory,
- HeuristicConfigPolicy configPolicy, List> queuedPlacerList) {
- super(factory, configPolicy);
- this.queuedPlacerList = queuedPlacerList;
- this.queuedPlacerList.forEach(queuedPlacer -> phaseLifecycleSupport.addEventListener(queuedPlacer));
-
- }
-
- @Override
- public EntityPlacer rebuildWithFilter(SelectionFilter filter) {
- var filteredQueuedPlacerList = queuedPlacerList.stream()
- .map(placer -> placer.rebuildWithFilter(filter))
- .toList();
- return new QueuedMultiplePlacer<>(factory, configPolicy, filteredQueuedPlacerList);
- }
-
- @Override
- public Iterator> iterator() {
- var iterator = new MultipleQueuedPlacingIterator(queuedPlacerList);
- phaseLifecycleSupport.addEventListener(iterator);
- return iterator;
- }
-
- private class MultipleQueuedPlacingIterator extends UpcomingSelectionIterator>
- implements PhaseLifecycleListener {
-
- private final List> queuedPlacerList;
- private Iterator>[] moveIterators;
- private Iterator>[] placementIterators;
- private Move[] previousMove;
- private Move cachedMove = null;
-
- private MultipleQueuedPlacingIterator(List> queuedPlacerList) {
- // We expect only the QueuedValuePlacer and a QueuedEntityPlacer
- var assertSize = queuedPlacerList.size() == 2;
- var assertQueuedValuePlacer =
- queuedPlacerList.stream().anyMatch(QueuedValuePlacer.class::isInstance);
- var assertQueuedEntityPlacer =
- queuedPlacerList.stream().anyMatch(QueuedEntityPlacer.class::isInstance);
- if (!assertSize || !assertQueuedValuePlacer || !assertQueuedEntityPlacer) {
- throw new IllegalArgumentException(
- "Impossible state: the queued placer list must consist exclusively of a QueuedValuePlacer and a QueuedEntityPlacer.");
- }
- this.queuedPlacerList = new ArrayList<>();
- // We make sure that the QueuedEntityPlacer is added first
- this.queuedPlacerList.addAll(queuedPlacerList.stream().filter(QueuedValuePlacer.class::isInstance).toList());
- this.queuedPlacerList.addAll(queuedPlacerList.stream().filter(QueuedEntityPlacer.class::isInstance).toList());
- reset();
- }
-
- /**
- * The method uses a strategy similar to {@link CartesianProductMoveSelector},
- * but it uses placer iterators instead.
- */
- private Move nextMove() {
- if (cachedMove != null) {
- return cachedMove;
- }
- var childSize = moveIterators.length;
- int index;
- Move[] move = new Move[childSize];
- if (previousMove == null) {
- index = -1;
- } else {
- index = consumeNextMove(move, previousMove);
- if (index == -1) {
- // No more moves
- return null;
- }
- }
- var updatedMove = updateNextIterators(index, move);
- if (updatedMove == null) {
- // We stop if one of the placement iterators has no next placement
- return null;
- }
- previousMove = updatedMove;
- cachedMove = CompositeMove.buildMove(updatedMove);
- return cachedMove;
- }
-
- /**
- * Go through the registered iterators and check for any available moves.
- *
- * @param move the move array to be loaded
- * @param previousMove the previous moves
- * @return the last index of the iterator that still has available moves; otherwise, the function returns -1
- * when all iterators have no more moves available.
- */
- private int consumeNextMove(Move>[] move, Move[] previousMove) {
- var index = move.length - 1;
- // Look for the first iterator that still has available moves to generate
- while (index >= 0) {
- var moveIterator = moveIterators[index];
- if (moveIterator.hasNext()) {
- break;
- }
- // Check if there are more placements available in the QueuedEntityPlacer
- if (index == 1) {
- var placementIterator = placementIterators[index];
- if (placementIterator.hasNext()) {
- moveIterators[index] = placementIterator.next().iterator();
- continue;
- } else {
- // Reset the iterator in case the previous placerIterator still has more placements
- placementIterators[index] = queuedPlacerList.get(index).iterator();
- }
- }
- index--;
- }
- if (index < 0) {
- return -1;
- }
- // Copy the previous move until the next one generated
- System.arraycopy(previousMove, 0, move, 0, index);
- // Generate and set the new move
- move[index] = moveIterators[index].next();
- return index;
- }
-
- /**
- * Update the move list and recreate all move iterators starting from #lastValidIteratorIndex.
- *
- * @param lastValidIteratorIndex the index of the last iterator that generated a valid move
- * @param move the move array to be loaded
- */
- private Move[] updateNextIterators(int lastValidIteratorIndex, Move>[] move) {
- var childSize = moveIterators.length;
- var updatedMove = new Move[childSize];
- System.arraycopy(move, 0, updatedMove, 0, childSize);
- for (int i = lastValidIteratorIndex + 1; i < childSize; i++) {
- var placementIterator = placementIterators[i];
- Move next;
- if (!placementIterator.hasNext()) {
- return null;
- } else {
- var moveIterator = placementIterator.next().iterator();
- moveIterators[i] = moveIterator;
- next = moveIterator.next();
- }
- updatedMove[i] = next;
- }
- return updatedMove;
- }
-
- private void clearCache() {
- this.cachedMove = null;
- }
-
- @SuppressWarnings("unchecked")
- private void reset() {
- if (moveIterators == null) {
- moveIterators = new Iterator[queuedPlacerList.size()];
- Arrays.fill(moveIterators, null);
- placementIterators = new Iterator[queuedPlacerList.size()];
- for (var i = 0; i < queuedPlacerList.size(); i++) {
- var placement = queuedPlacerList.get(i);
- placementIterators[i] = placement.iterator();
- }
- } else {
- Arrays.fill(moveIterators, null);
- // We need to reset of the QueuedEntityPlacer or there will be no more moves for the basic variables
- placementIterators[1] = queuedPlacerList.get(1).iterator();
- }
- previousMove = null;
- }
-
- @Override
- protected Placement createUpcomingSelection() {
- var nextMove = nextMove();
- if (nextMove == null) {
- return noUpcomingSelection();
- }
- return new Placement<>(new PlacementToMoveAdapterIterator(this));
- }
-
- @Override
- public void phaseStarted(AbstractPhaseScope phaseScope) {
- // Ignore
- }
-
- @Override
- public void stepStarted(AbstractStepScope stepScope) {
- // Ignore
- }
-
- @Override
- public void stepEnded(AbstractStepScope stepScope) {
- reset();
- }
-
- @Override
- public void phaseEnded(AbstractPhaseScope phaseScope) {
- // Ignore
- }
-
- @Override
- public void solvingStarted(SolverScope solverScope) {
- // Ignore
- }
-
- @Override
- public void solvingEnded(SolverScope solverScope) {
- // Ignore
- }
- }
-
- private class PlacementToMoveAdapterIterator implements Iterator> {
- private final MultipleQueuedPlacingIterator iterator;
-
- private PlacementToMoveAdapterIterator(MultipleQueuedPlacingIterator iterator) {
- this.iterator = iterator;
- }
-
- @Override
- public boolean hasNext() {
- return iterator.nextMove() != null;
- }
-
- @Override
- public Move next() {
- var move = iterator.nextMove();
- iterator.clearCache();
- return move;
- }
- }
-}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacerFactory.java
deleted file mode 100644
index 6a08c8a08de..00000000000
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedMultiplePlacerFactory.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package ai.timefold.solver.core.impl.constructionheuristic.placer;
-
-import java.util.ArrayList;
-
-import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig;
-import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
-
-public final class QueuedMultiplePlacerFactory
- extends AbstractEntityPlacerFactory {
-
- public QueuedMultiplePlacerFactory(QueuedMultiplePlacerConfig config) {
- super(config);
- }
-
- @Override
- public EntityPlacer buildEntityPlacer(HeuristicConfigPolicy configPolicy) {
- var queuedPlacerList = new ArrayList>();
- for (var placerConfig : config.getPlacerConfigList()) {
- var placer = EntityPlacerFactory. create(placerConfig).buildEntityPlacer(configPolicy);
- queuedPlacerList.add(placer);
- }
- return new QueuedMultiplePlacer<>(this, configPolicy, queuedPlacerList);
- }
-}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java
index 8a5b6ebf3c4..ba604317e0d 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacer.java
@@ -8,6 +8,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory;
+import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.EntityIndependentFilteringValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector;
@@ -31,6 +32,10 @@ public Iterator> iterator() {
return new QueuedValuePlacingIterator();
}
+ public boolean hasListChangeMoveSelector() {
+ return moveSelector instanceof ListChangeMoveSelector;
+ }
+
private class QueuedValuePlacingIterator extends UpcomingSelectionIterator> {
private Iterator