Skip to content

feat: enable Basic var and List var to coexist for the local search #1606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 maxOccurs="unbounded" minOccurs="0">
<xs:choice minOccurs="0">


<xs:element name="queuedEntityPlacer" type="tns:queuedEntityPlacerConfig"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.config.constructionheuristic;

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

Expand Down Expand Up @@ -37,7 +36,7 @@
"constructionHeuristicType",
"entitySorterManner",
"valueSorterManner",
"entityPlacerConfigList",
"entityPlacerConfig",
"moveSelectorConfigList",
"foragerConfig"
})
Expand All @@ -57,9 +56,9 @@ public class ConstructionHeuristicPhaseConfig extends PhaseConfig<ConstructionHe
@XmlElement(name = "queuedValuePlacer", type = QueuedValuePlacerConfig.class),
@XmlElement(name = "pooledEntityPlacer", type = PooledEntityPlacerConfig.class)
})
protected List<EntityPlacerConfig> 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),
Expand Down Expand Up @@ -111,35 +110,12 @@ public void setValueSorterManner(@Nullable ValueSorterManner valueSorterManner)
this.valueSorterManner = valueSorterManner;
}

public List<EntityPlacerConfig> getEntityPlacerConfigList() {
return entityPlacerConfigList;
}

public void setEntityPlacerConfigList(List<EntityPlacerConfig> 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() {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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());
Expand All @@ -233,8 +198,8 @@ public void visitReferencedClasses(@NonNull Consumer<Class<?>> 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));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.impl;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

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

public static <Solution_> EntitySelectorConfig getDefaultEntitySelectorConfigForEntity(
HeuristicConfigPolicy<Solution_> configPolicy, EntityDescriptor<Solution_> 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);
Expand All @@ -44,15 +43,15 @@ public static <Solution_> EntitySelectorConfig deduceEntitySortManner(HeuristicC

protected EntityDescriptor<Solution_> deduceEntityDescriptor(HeuristicConfigPolicy<Solution_> configPolicy,
Class<?> entityClass) {
SolutionDescriptor<Solution_> solutionDescriptor = configPolicy.getSolutionDescriptor();
var solutionDescriptor = configPolicy.getSolutionDescriptor();
return entityClass == null
? getTheOnlyEntityDescriptor(solutionDescriptor)
: getEntityDescriptorForClass(solutionDescriptor, entityClass);
}

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

protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescriptor<Solution_> solutionDescriptor) {
Collection<EntityDescriptor<Solution_>> 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."
Expand All @@ -76,7 +75,7 @@ protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescrip

protected EntityDescriptor<Solution_>
getTheOnlyEntityDescriptorWithBasicVariables(SolutionDescriptor<Solution_> solutionDescriptor) {
Collection<EntityDescriptor<Solution_>> entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
var entityDescriptors = solutionDescriptor.getGenuineEntityDescriptors()
.stream()
.filter(EntityDescriptor::hasAnyGenuineBasicVariables)
.toList();
Expand All @@ -88,6 +87,20 @@ protected EntityDescriptor<Solution_> getTheOnlyEntityDescriptor(SolutionDescrip
return entityDescriptors.iterator().next();
}

protected EntityDescriptor<Solution_>
getTheOnlyEntityDescriptorWithListVariable(SolutionDescriptor<Solution_> 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<Solution_> deduceGenuineVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor,
String variableName) {
return variableName == null
Expand All @@ -97,7 +110,7 @@ protected GenuineVariableDescriptor<Solution_> deduceGenuineVariableDescriptor(E

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

protected GenuineVariableDescriptor<Solution_> getTheOnlyVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor) {
List<GenuineVariableDescriptor<Solution_>> 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."
Expand All @@ -123,8 +135,26 @@ protected GenuineVariableDescriptor<Solution_> getTheOnlyVariableDescriptor(Enti
protected List<GenuineVariableDescriptor<Solution_>> deduceVariableDescriptorList(
EntityDescriptor<Solution_> entityDescriptor, List<String> variableNameIncludeList) {
Objects.requireNonNull(entityDescriptor);
List<GenuineVariableDescriptor<Solution_>> 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<GenuineVariableDescriptor<Solution_>> deduceBasicVariableDescriptorList(
EntityDescriptor<Solution_> entityDescriptor, List<String> variableNameIncludeList) {
Objects.requireNonNull(entityDescriptor);
var variableDescriptorList = entityDescriptor.getGenuineBasicVariableDescriptorList();
if (variableNameIncludeList == null) {
return variableDescriptorList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void solve(SolverScope<Solution_> 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.
Expand Down
Loading
Loading