Skip to content
Merged
2 changes: 1 addition & 1 deletion benchmark/src/main/resources/benchmark.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@
<xs:element minOccurs="0" name="valueSorterManner" type="tns:valueSorterManner"/>


<xs:choice minOccurs="0">
<xs:choice maxOccurs="unbounded" minOccurs="0">
Comment thread
triceo marked this conversation as resolved.


<xs:element name="queuedEntityPlacer" type="tns:queuedEntityPlacerConfig"/>
Expand Down
17 changes: 17 additions & 0 deletions core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,23 @@
"new": "class ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore",
"annotation": "@org.jspecify.annotations.NullMarked",
"justification": "@NonNull replaced by @NullMarked"
},
{
"ignore": true,
"code": "java.field.removed",
"old": "field ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig.entityPlacerConfig",
"justification": "New CH configuration with multiple placers"
},
{
"ignore": true,
"code": "java.annotation.attributeValueChanged",
"old": "class ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig",
"new": "class ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig",
"annotationType": "jakarta.xml.bind.annotation.XmlType",
"attribute": "propOrder",
"oldValue": "{\"constructionHeuristicType\", \"entitySorterManner\", \"valueSorterManner\", \"entityPlacerConfig\", \"moveSelectorConfigList\", \"foragerConfig\"}",
"newValue": "{\"constructionHeuristicType\", \"entitySorterManner\", \"valueSorterManner\", \"entityPlacerConfigList\", \"moveSelectorConfigList\", \"foragerConfig\"}",
"justification": "New CH configuration with multiple placers"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ai.timefold.solver.core.config.constructionheuristic;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

Expand Down Expand Up @@ -36,7 +37,7 @@
"constructionHeuristicType",
"entitySorterManner",
"valueSorterManner",
"entityPlacerConfig",
"entityPlacerConfigList",
Comment thread
zepfred marked this conversation as resolved.
"moveSelectorConfigList",
"foragerConfig"
})
Expand All @@ -56,9 +57,9 @@ public class ConstructionHeuristicPhaseConfig extends PhaseConfig<ConstructionHe
@XmlElement(name = "queuedValuePlacer", type = QueuedValuePlacerConfig.class),
@XmlElement(name = "pooledEntityPlacer", type = PooledEntityPlacerConfig.class)
})
protected EntityPlacerConfig entityPlacerConfig = null;
protected List<EntityPlacerConfig> entityPlacerConfigList = null;

/** Simpler alternative for {@link #entityPlacerConfig}. */
/** Simpler alternative for {@link #entityPlacerConfigList}. */
@XmlElements({
@XmlElement(name = CartesianProductMoveSelectorConfig.XML_ELEMENT_NAME,
type = CartesianProductMoveSelectorConfig.class),
Expand Down Expand Up @@ -110,12 +111,35 @@ public void setValueSorterManner(@Nullable ValueSorterManner valueSorterManner)
this.valueSorterManner = valueSorterManner;
}

public @Nullable EntityPlacerConfig getEntityPlacerConfig() {
return entityPlacerConfig;
public List<EntityPlacerConfig> getEntityPlacerConfigList() {
return entityPlacerConfigList;
}

public void setEntityPlacerConfigList(List<EntityPlacerConfig> entityPlacerConfigList) {
this.entityPlacerConfigList = entityPlacerConfigList;
}

public void setEntityPlacerConfig(@Nullable EntityPlacerConfig entityPlacerConfig) {
this.entityPlacerConfig = entityPlacerConfig;
/**
* @deprecated Use {@link #setEntityPlacerConfigList(List)}} instead.
*/
@Deprecated(forRemoval = true, since = "1.22.0")
public void setEntityPlacerConfig(EntityPlacerConfig entityPlacerConfig) {
setEntityPlacerConfigList(List.of(entityPlacerConfig));
}
Comment thread
triceo marked this conversation as resolved.

/**
* @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 @Nullable List<@NonNull MoveSelectorConfig> getMoveSelectorConfigList() {
Expand Down Expand Up @@ -154,8 +178,19 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
return this;
}

public @NonNull ConstructionHeuristicPhaseConfig withEntityPlacerConfig(@NonNull EntityPlacerConfig<?> entityPlacerConfig) {
this.entityPlacerConfig = entityPlacerConfig;
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));
return this;
}

Expand All @@ -180,8 +215,8 @@ public void setForagerConfig(@Nullable ConstructionHeuristicForagerConfig forage
inheritedConfig.getEntitySorterManner());
valueSorterManner = ConfigUtils.inheritOverwritableProperty(valueSorterManner,
inheritedConfig.getValueSorterManner());
setEntityPlacerConfig(ConfigUtils.inheritOverwritableProperty(
getEntityPlacerConfig(), inheritedConfig.getEntityPlacerConfig()));
entityPlacerConfigList = ConfigUtils.inheritMergeableListConfig(
entityPlacerConfigList, inheritedConfig.getEntityPlacerConfigList());
moveSelectorConfigList = ConfigUtils.inheritMergeableListConfig(
moveSelectorConfigList, inheritedConfig.getMoveSelectorConfigList());
foragerConfig = ConfigUtils.inheritConfig(foragerConfig, inheritedConfig.getForagerConfig());
Expand All @@ -198,8 +233,8 @@ public void visitReferencedClasses(@NonNull Consumer<Class<?>> classVisitor) {
if (terminationConfig != null) {
terminationConfig.visitReferencedClasses(classVisitor);
}
if (entityPlacerConfig != null) {
entityPlacerConfig.visitReferencedClasses(classVisitor);
if (entityPlacerConfigList != null) {
entityPlacerConfigList.forEach(entityPlacerConfig -> entityPlacerConfig.visitReferencedClasses(classVisitor));
}
if (moveSelectorConfigList != null) {
moveSelectorConfigList.forEach(ms -> ms.visitReferencedClasses(classVisitor));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
})
public class QueuedEntityPlacerConfig extends EntityPlacerConfig<QueuedEntityPlacerConfig> {

public static final String XML_ELEMENT_NAME = "queuedEntityPlacer";

@XmlElement(name = "entitySelector")
protected EntitySelectorConfig entitySelectorConfig = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
})
public class QueuedValuePlacerConfig extends EntityPlacerConfig<QueuedValuePlacerConfig> {

public static final String XML_ELEMENT_NAME = "queuedValuePlacer";

protected Class<?> entityClass = null;

@XmlElement(name = "valueSelector")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

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

protected final Config_ config;

public AbstractFromConfigFactory(Config_ config) {
protected AbstractFromConfigFactory(Config_ config) {
this.config = config;
}

Expand Down Expand Up @@ -55,23 +54,36 @@ private EntityDescriptor<Solution_> getEntityDescriptorForClass(SolutionDescript
Class<?> entityClass) {
EntityDescriptor<Solution_> entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass);
if (entityDescriptor == null) {
throw new IllegalArgumentException("The config (" + config
+ ") has an entityClass (" + entityClass + ") that is not a known planning entity.\n"
+ "Check your solver configuration. If that class (" + entityClass.getSimpleName()
+ ") is not in the entityClassSet (" + solutionDescriptor.getEntityClassSet()
+ "), check your @" + PlanningSolution.class.getSimpleName()
+ " implementation's annotated methods too.");
throw new IllegalArgumentException(
"""
The config (%s) has an entityClass (%s) that is not a known planning entity.
Check your solver configuration. If that class (%s) is not in the entityClassSet (%s), check your @%s implementation's annotated methods too."""
.formatted(config, entityClass, entityClass.getSimpleName(), solutionDescriptor.getEntityClassSet(),
PlanningSolution.class.getSimpleName()));
}
return entityDescriptor;
}

protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescriptor<Solution_> solutionDescriptor) {
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors();
if (entityDescriptors.size() != 1) {
throw new IllegalArgumentException("The config (" + config
+ ") has no entityClass configured and because there are multiple in the entityClassSet ("
+ solutionDescriptor.getEntityClassSet()
+ "), it cannot be deduced automatically.");
throw new IllegalArgumentException(
"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 EntityDescriptor<Solution_>
getTheOnlyEntityDescriptorWithBasicVariables(SolutionDescriptor<Solution_> solutionDescriptor) {
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
.stream()
.filter(EntityDescriptor::hasAnyGenuineBasicVariables)
.toList();
if (entityDescriptors.size() != 1) {
throw new IllegalArgumentException(
"The config (%s) has no entityClass configured and because there are multiple in the entityClassSet (%s) defining basic variables, it cannot be deduced automatically."
.formatted(config, solutionDescriptor.getEntityClassSet()));
}
return entityDescriptors.iterator().next();
}
Expand All @@ -87,11 +99,11 @@ protected GenuineVariableDescriptor<Solution_> getVariableDescriptorForName(Enti
String variableName) {
GenuineVariableDescriptor<Solution_> variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
if (variableDescriptor == null) {
throw new IllegalArgumentException("The config (" + config
+ ") has a variableName (" + variableName
+ ") which is not a valid planning variable on entityClass ("
+ entityDescriptor.getEntityClass() + ").\n"
+ entityDescriptor.buildInvalidVariableNameExceptionMessage(variableName));
throw new IllegalArgumentException(
"""
The config (%s) has a variableName (%s) which is not a valid planning variable on entityClass (%s).
%s""".formatted(config, variableName, entityDescriptor.getEntityClass(),
entityDescriptor.buildInvalidVariableNameExceptionMessage(variableName)));
}
return variableDescriptor;
}
Expand All @@ -100,11 +112,10 @@ protected GenuineVariableDescriptor<Solution_> getTheOnlyVariableDescriptor(Enti
List<GenuineVariableDescriptor<Solution_>> variableDescriptorList =
entityDescriptor.getGenuineVariableDescriptorList();
if (variableDescriptorList.size() != 1) {
throw new IllegalArgumentException("The config (" + config
+ ") has no configured variableName for entityClass (" + entityDescriptor.getEntityClass()
+ ") and because there are multiple variableNames ("
+ entityDescriptor.getGenuineVariableNameSet()
+ "), it cannot be deduced automatically.");
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."
.formatted(config, entityDescriptor.getEntityClass(),
entityDescriptor.getGenuineVariableNameSet()));
}
return variableDescriptorList.iterator().next();
}
Expand All @@ -122,10 +133,10 @@ protected List<GenuineVariableDescriptor<Solution_>> deduceVariableDescriptorLis
.map(variableNameInclude -> variableDescriptorList.stream()
.filter(variableDescriptor -> variableDescriptor.getVariableName().equals(variableNameInclude))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("The config (" + config
+ ") has a variableNameInclude (" + variableNameInclude
+ ") which does not exist in the entity (" + entityDescriptor.getEntityClass()
+ ")'s variableDescriptorList (" + variableDescriptorList + ").")))
.collect(Collectors.toList());
.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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,14 @@ public void solve(SolverScope<Solution_> solverScope) {
phaseStarted(phaseScope);

var solutionDescriptor = solverScope.getSolutionDescriptor();
var listVariableDescriptor = solutionDescriptor.getListVariableDescriptor();
var hasListVariable = listVariableDescriptor != null;
var hasListVariable = solutionDescriptor.hasListVariable();
var maxStepCount = -1;
if (hasListVariable) {
// In case of list variable with support for unassigned values, the placer will iterate indefinitely.
// (When it exhausts all values, it will start over from the beginning.)
// To prevent that, we need to limit the number of steps to the number of unassigned values.
var workingSolution = phaseScope.getWorkingSolution();
maxStepCount = listVariableDescriptor.countUnassigned(workingSolution);
maxStepCount = solutionDescriptor.getListVariableDescriptor().countUnassigned(workingSolution);
}

TerminationStatus earlyTerminationStatus = null;
Expand Down
Loading
Loading