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 valueIterator; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java index 76a6332449f..3471f33a814 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java @@ -99,6 +99,7 @@ protected ChangeMoveSelectorConfig buildChangeMoveSelectorConfig( .withSorterManner(configPolicy.getEntitySorterManner()); } ValueSelectorConfig changeValueSelectorConfig = new ValueSelectorConfig() + .withVariableName(variableDescriptor.getVariableName()) .withMimicSelectorRef(valueSelectorConfigId); return changeMoveSelectorConfig.withEntitySelectorConfig(changeEntitySelectorConfig) .withValueSelectorConfig(changeValueSelectorConfig); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/internal/QueuedMultiplePlacerConfig.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/internal/QueuedMultiplePlacerConfig.java deleted file mode 100644 index a2d597e98f3..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/internal/QueuedMultiplePlacerConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package ai.timefold.solver.core.impl.constructionheuristic.placer.internal; - -import java.util.List; -import java.util.function.Consumer; - -import ai.timefold.solver.core.config.constructionheuristic.placer.EntityPlacerConfig; -import ai.timefold.solver.core.config.util.ConfigUtils; - -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -public class QueuedMultiplePlacerConfig extends EntityPlacerConfig { - - protected List placerConfigList = null; - - public List getPlacerConfigList() { - return placerConfigList; - } - - public void setPlacerConfigList(List placerConfigList) { - this.placerConfigList = placerConfigList; - } - - // ************************************************************************ - // With methods - // ************************************************************************ - - public @NonNull QueuedMultiplePlacerConfig - withPlacerConfigList(@NonNull List<@NonNull EntityPlacerConfig> placerConfigList) { - setPlacerConfigList(placerConfigList); - return this; - } - - // ************************************************************************ - // Worker methods - // ************************************************************************ - - @Override - public @NonNull QueuedMultiplePlacerConfig - inherit(@NonNull QueuedMultiplePlacerConfig inheritedConfig) { - placerConfigList = - ConfigUtils.inheritMergeableListConfig(placerConfigList, inheritedConfig.getPlacerConfigList()); - return this; - } - - @Override - public @NonNull QueuedMultiplePlacerConfig copyConfig() { - return new QueuedMultiplePlacerConfig().inherit(this); - } - - @Override - public void visitReferencedClasses(@NonNull Consumer<@Nullable Class> classVisitor) { - if (placerConfigList != null) { - placerConfigList.forEach(placer -> placer.visitReferencedClasses(classVisitor)); - } - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java index e8b5c2a5404..62664e3535d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java @@ -699,6 +699,13 @@ public boolean hasAnyGenuineVariables() { return !effectiveGenuineVariableDescriptorMap.isEmpty(); } + public boolean hasBothGenuineListAndBasicVariables() { + if (!isGenuine()) { + return false; + } + return hasAnyGenuineListVariables() && hasAnyGenuineBasicVariables(); + } + public boolean hasAnyGenuineBasicVariables() { if (!isGenuine()) { return false; @@ -740,6 +747,12 @@ public List> getGenuineVariableDescriptorLi return effectiveGenuineVariableDescriptorList; } + public List> getGenuineBasicVariableDescriptorList() { + return effectiveGenuineVariableDescriptorList.stream() + .filter(descriptor -> !descriptor.isListVariable()) + .toList(); + } + public long getGenuineVariableCount() { return effectiveGenuineVariableDescriptorList.size(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java index bb99763daf8..5913547d3fa 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java @@ -894,8 +894,15 @@ public PlanningSolutionMetaModel getMetaModel() { return planningSolutionMetaModel; } + public List> getBasicVariableDescriptorList() { + return getGenuineEntityDescriptors().stream() + .flatMap(entityDescriptor -> entityDescriptor.getGenuineBasicVariableDescriptorList().stream()) + .map(descriptor -> (BasicVariableDescriptor) descriptor) + .toList(); + } + public boolean hasBasicVariable() { - return getGenuineEntityDescriptors().stream().anyMatch(EntityDescriptor::hasAnyGenuineBasicVariables); + return !getBasicVariableDescriptorList().isEmpty(); } public boolean hasChainedVariable() { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java index f769c37f62b..07534bca2a6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java @@ -28,7 +28,7 @@ public class HeuristicConfigPolicy { - private final Set previewFeatureList; + private final Set previewFeatureSet; private final EnvironmentMode environmentMode; private final String logIndentation; private final Integer moveThreadCount; @@ -50,7 +50,7 @@ public class HeuristicConfigPolicy { private final Map> valueMimicRecorderMap = new HashMap<>(); private HeuristicConfigPolicy(Builder builder) { - this.previewFeatureList = builder.previewFeatureList; + this.previewFeatureSet = builder.previewFeatureSet; this.environmentMode = builder.environmentMode; this.logIndentation = builder.logIndentation; this.moveThreadCount = builder.moveThreadCount; @@ -134,7 +134,7 @@ public Random getRandom() { public Builder cloneBuilder() { return new Builder() - .withPreviewFeatureList(previewFeatureList) + .withPreviewFeatureSet(previewFeatureSet) .withEnvironmentMode(environmentMode) .withMoveThreadCount(moveThreadCount) .withMoveThreadBufferSize(moveThreadBufferSize) @@ -229,11 +229,12 @@ public ThreadFactory buildThreadFactory(ChildThreadType childThreadType) { } public void ensurePreviewFeature(PreviewFeature previewFeature) { - ensurePreviewFeature(previewFeature, previewFeatureList); + ensurePreviewFeature(previewFeature, previewFeatureSet); } - public static void ensurePreviewFeature(PreviewFeature previewFeature, Collection previewFeatureList) { - if (previewFeatureList == null || !previewFeatureList.contains(previewFeature)) { + public static void ensurePreviewFeature(PreviewFeature previewFeature, + Collection previewFeatureCollection) { + if (previewFeatureCollection == null || !previewFeatureCollection.contains(previewFeature)) { throw new IllegalStateException(""" The preview feature %s is not enabled. Maybe add %s to in your configuration file?""" @@ -248,7 +249,7 @@ public String toString() { public static class Builder { - private Set previewFeatureList; + private Set previewFeatureSet; private EnvironmentMode environmentMode; private Integer moveThreadCount; private Integer moveThreadBufferSize; @@ -269,8 +270,8 @@ public static class Builder { private Class> nearbyDistanceMeterClass; private Random random; - public Builder withPreviewFeatureList(Set previewFeatureList) { - this.previewFeatureList = previewFeatureList; + public Builder withPreviewFeatureSet(Set previewFeatureSet) { + this.previewFeatureSet = previewFeatureSet; return this; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java index 81c3dddc997..13dabfbf431 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java @@ -48,15 +48,15 @@ public SubListSelector buildSubListSelector(HeuristicConfigPolicy valueSelector = buildEntityIndependentValueSelector(configPolicy, - entitySelector.getEntityDescriptor(), minimumCacheType, inheritedSelectionOrder); + var valueSelector = buildEntityIndependentValueSelector(configPolicy, entitySelector.getEntityDescriptor(), + minimumCacheType, inheritedSelectionOrder); - int minimumSubListSize = Objects.requireNonNullElse(config.getMinimumSubListSize(), DEFAULT_MINIMUM_SUB_LIST_SIZE); - int maximumSubListSize = Objects.requireNonNullElse(config.getMaximumSubListSize(), DEFAULT_MAXIMUM_SUB_LIST_SIZE); - RandomSubListSelector baseSubListSelector = + var minimumSubListSize = Objects.requireNonNullElse(config.getMinimumSubListSize(), DEFAULT_MINIMUM_SUB_LIST_SIZE); + var maximumSubListSize = Objects.requireNonNullElse(config.getMaximumSubListSize(), DEFAULT_MAXIMUM_SUB_LIST_SIZE); + var baseSubListSelector = new RandomSubListSelector<>(entitySelector, valueSelector, minimumSubListSize, maximumSubListSize); - SubListSelector subListSelector = + var subListSelector = applyNearbySelection(configPolicy, minimumCacheType, inheritedSelectionOrder, baseSubListSelector); subListSelector = applyMimicRecording(configPolicy, subListSelector); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/AbstractCompositeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/AbstractCompositeMoveSelectorFactory.java index a0005b91e71..45e9592edd4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/AbstractCompositeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/AbstractCompositeMoveSelectorFactory.java @@ -1,7 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.composite; import java.util.List; -import java.util.stream.Collectors; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -14,7 +13,7 @@ abstract class AbstractCompositeMoveSelectorFactory> extends AbstractMoveSelectorFactory { - public AbstractCompositeMoveSelectorFactory(MoveSelectorConfig_ moveSelectorConfig) { + protected AbstractCompositeMoveSelectorFactory(MoveSelectorConfig_ moveSelectorConfig) { super(moveSelectorConfig); } @@ -24,8 +23,8 @@ protected List> buildInnerMoveSelectors(List { AbstractMoveSelectorFactory moveSelectorFactory = MoveSelectorFactory.create(moveSelectorConfig); - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); return moveSelectorFactory.buildMoveSelector(configPolicy, minimumCacheType, selectionOrder, false); - }).collect(Collectors.toList()); + }).toList(); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java index f7d18cb3c21..45e77a1b982 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java @@ -18,12 +18,10 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMoveSelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory; import org.slf4j.Logger; @@ -43,11 +41,11 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy entitySelector = EntitySelectorFactory + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var entitySelector = EntitySelectorFactory . create(config.getEntitySelectorConfig()) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder); - ValueSelector valueSelector = ValueSelectorFactory + var valueSelector = ValueSelectorFactory . create(config.getValueSelectorConfig()) .buildValueSelector(configPolicy, entitySelector.getEntityDescriptor(), minimumCacheType, selectionOrder); return new ChangeMoveSelector<>(entitySelector, valueSelector, randomSelection); @@ -56,7 +54,7 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { Collection> entityDescriptors; - EntityDescriptor onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null + var onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null : EntitySelectorFactory. create(config.getEntitySelectorConfig()) .extractEntityDescriptor(configPolicy); if (onlyEntityDescriptor != null) { @@ -64,9 +62,9 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP } else { entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); } - List> variableDescriptorList = new ArrayList<>(); + var variableDescriptorList = new ArrayList>(); for (EntityDescriptor entityDescriptor : entityDescriptors) { - GenuineVariableDescriptor onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null + var onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null : ValueSelectorFactory. create(config.getValueSelectorConfig()) .extractVariableDescriptor(configPolicy, entityDescriptor); if (onlyVariableDescriptor != null) { @@ -82,28 +80,35 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP variableDescriptorList.addAll(entityDescriptor.getGenuineVariableDescriptorList()); } } - return buildUnfoldedMoveSelectorConfig(variableDescriptorList); + return buildUnfoldedMoveSelectorConfig(configPolicy, variableDescriptorList); } - protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig( + protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy, List> variableDescriptorList) { - List moveSelectorConfigList = new ArrayList<>(variableDescriptorList.size()); - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { + var moveSelectorConfigList = new ArrayList(variableDescriptorList.size()); + for (var variableDescriptor : variableDescriptorList) { if (variableDescriptor.isListVariable()) { + if (configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()) { + // When using a mixed model, + // we do not create a list move + // and delegate it to the ListChangeMoveSelectorFactory. + // The strategy aims to provide a more normalized move selector collection for mixed models. + continue; + } // No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded() - ListChangeMoveSelectorConfig childMoveSelectorConfig = + var childMoveSelectorConfig = buildListChangeMoveSelectorConfig((ListVariableDescriptor) variableDescriptor, false); moveSelectorConfigList.add(childMoveSelectorConfig); } else { // No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded() - ChangeMoveSelectorConfig childMoveSelectorConfig = new ChangeMoveSelectorConfig(); + var childMoveSelectorConfig = new ChangeMoveSelectorConfig(); // Different EntitySelector per child because it is a union - EntitySelectorConfig childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig()); + var childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig()); if (childEntitySelectorConfig.getMimicSelectorRef() == null) { childEntitySelectorConfig.setEntityClass(variableDescriptor.getEntityDescriptor().getEntityClass()); } childMoveSelectorConfig.setEntitySelectorConfig(childEntitySelectorConfig); - ValueSelectorConfig childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig()); + var childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig()); if (childValueSelectorConfig.getMimicSelectorRef() == null) { childValueSelectorConfig.setVariableName(variableDescriptor.getVariableName()); } @@ -130,8 +135,8 @@ The changeMoveSelectorConfig ({}) is being used for a list variable. We are keeping this option through the 1.x release stream for backward compatibility reasons. Please update your solver config to use {} now.""", config, ListChangeMoveSelectorConfig.class.getSimpleName()); - ListChangeMoveSelectorConfig listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig( - variableDescriptor, config.getValueSelectorConfig(), createDestinationSelectorConfig()); + var listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig(variableDescriptor, + config.getValueSelectorConfig(), createDestinationSelectorConfig()); if (inheritFoldedConfig) { listChangeMoveSelectorConfig.inheritFolded(config); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelectorFactory.java index 3d07d503343..28f031d9a50 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveSelectorFactory.java @@ -8,6 +8,7 @@ import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.entity.pillar.PillarSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarSwapMoveSelectorConfig; +import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.entity.pillar.PillarSelector; @@ -25,27 +26,59 @@ public PillarSwapMoveSelectorFactory(PillarSwapMoveSelectorConfig moveSelectorCo @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - PillarSelectorConfig leftPillarSelectorConfig = + var leftPillarSelectorConfig = Objects.requireNonNullElseGet(config.getPillarSelectorConfig(), PillarSelectorConfig::new); - PillarSelectorConfig rightPillarSelectorConfig = - Objects.requireNonNullElse(config.getSecondaryPillarSelectorConfig(), leftPillarSelectorConfig); - PillarSelector leftPillarSelector = - buildPillarSelector(leftPillarSelectorConfig, configPolicy, minimumCacheType, randomSelection); - PillarSelector rightPillarSelector = - buildPillarSelector(rightPillarSelectorConfig, configPolicy, minimumCacheType, randomSelection); - - List> variableDescriptorList = - deduceVariableDescriptorList(leftPillarSelector.getEntityDescriptor(), config.getVariableNameIncludeList()); + EntityDescriptor leftEntityDescriptor = null; + var leftEntitySelectorConfig = leftPillarSelectorConfig.getEntitySelectorConfig(); + if (leftEntitySelectorConfig != null && leftEntitySelectorConfig.getEntityClass() != null) { + leftEntityDescriptor = + configPolicy.getSolutionDescriptor().findEntityDescriptor(leftEntitySelectorConfig.getEntityClass()); + } + var leftVariableNameIncludeList = config.getVariableNameIncludeList(); + if (leftVariableNameIncludeList == null && leftEntityDescriptor != null + && leftEntityDescriptor.hasAnyGenuineBasicVariables() && leftEntityDescriptor.hasAnyGenuineListVariables()) { + // Mixed models filter out the list variable + leftVariableNameIncludeList = leftEntityDescriptor.getGenuineBasicVariableDescriptorList().stream() + .map(GenuineVariableDescriptor::getVariableName) + .toList(); + } + var rightPillarSelectorConfig = config.getSecondaryPillarSelectorConfig(); + if (rightPillarSelectorConfig == null) { + rightPillarSelectorConfig = leftPillarSelectorConfig; + } + EntityDescriptor rightEntityDescriptor = null; + var rightEntitySelectorConfig = rightPillarSelectorConfig.getEntitySelectorConfig(); + if (rightEntitySelectorConfig != null && rightEntitySelectorConfig.getEntityClass() != null) { + rightEntityDescriptor = + configPolicy.getSolutionDescriptor().findEntityDescriptor(rightEntitySelectorConfig.getEntityClass()); + } + var rightVariableNameIncludeList = config.getVariableNameIncludeList(); + if (rightVariableNameIncludeList == null && rightEntityDescriptor != null + && rightEntityDescriptor.hasAnyGenuineBasicVariables() && rightEntityDescriptor.hasAnyGenuineListVariables()) { + // Mixed models filter out the list variable + rightVariableNameIncludeList = rightEntityDescriptor.getGenuineBasicVariableDescriptorList().stream() + .map(GenuineVariableDescriptor::getVariableName) + .toList(); + } + var leftPillarSelector = + buildPillarSelector(leftPillarSelectorConfig, configPolicy, leftVariableNameIncludeList, minimumCacheType, + randomSelection); + var rightPillarSelector = + buildPillarSelector(rightPillarSelectorConfig, configPolicy, rightVariableNameIncludeList, minimumCacheType, + randomSelection); + + var variableDescriptorList = + deduceVariableDescriptorList(leftPillarSelector.getEntityDescriptor(), leftVariableNameIncludeList); return new PillarSwapMoveSelector<>(leftPillarSelector, rightPillarSelector, variableDescriptorList, randomSelection); } private PillarSelector buildPillarSelector(PillarSelectorConfig pillarSelectorConfig, - HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { + HeuristicConfigPolicy configPolicy, List variableNameIncludeList, + SelectionCacheType minimumCacheType, boolean randomSelection) { return PillarSelectorFactory. create(pillarSelectorConfig) .buildPillarSelector(configPolicy, config.getSubPillarType(), (Class>) config.getSubPillarSequenceComparatorClass(), minimumCacheType, - SelectionOrder.fromRandomSelectionBoolean(randomSelection), - config.getVariableNameIncludeList()); + SelectionOrder.fromRandomSelectionBoolean(randomSelection), variableNameIncludeList); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java index 54fa93a1561..3b53534c250 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java @@ -2,10 +2,8 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.Objects; -import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; @@ -15,10 +13,8 @@ import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -38,20 +34,19 @@ public SwapMoveSelectorFactory(SwapMoveSelectorConfig moveSelectorConfig) { @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - EntitySelectorConfig entitySelectorConfig = + var entitySelectorConfig = Objects.requireNonNullElseGet(config.getEntitySelectorConfig(), EntitySelectorConfig::new); - EntitySelectorConfig secondaryEntitySelectorConfig = + var secondaryEntitySelectorConfig = Objects.requireNonNullElse(config.getSecondaryEntitySelectorConfig(), entitySelectorConfig); - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - EntitySelector leftEntitySelector = + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var leftEntitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder); - EntitySelector rightEntitySelector = + var rightEntitySelector = EntitySelectorFactory. create(secondaryEntitySelectorConfig) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder); - EntityDescriptor entityDescriptor = leftEntitySelector.getEntityDescriptor(); - List> variableDescriptorList = - deduceVariableDescriptorList(entityDescriptor, config.getVariableNameIncludeList()); + var entityDescriptor = leftEntitySelector.getEntityDescriptor(); + var variableDescriptorList = deduceBasicVariableDescriptorList(entityDescriptor, config.getVariableNameIncludeList()); return new SwapMoveSelector<>(leftEntitySelector, rightEntitySelector, variableDescriptorList, randomSelection); @@ -59,61 +54,65 @@ EntitySelectorFactory. create(secondaryEntitySelectorConfig) @Override protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { - EntityDescriptor onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null + var onlyEntityDescriptor = config.getEntitySelectorConfig() == null ? null : EntitySelectorFactory. create(config.getEntitySelectorConfig()) .extractEntityDescriptor(configPolicy); if (config.getSecondaryEntitySelectorConfig() != null) { - EntityDescriptor onlySecondaryEntityDescriptor = + var onlySecondaryEntityDescriptor = EntitySelectorFactory. create(config.getSecondaryEntitySelectorConfig()) .extractEntityDescriptor(configPolicy); if (onlyEntityDescriptor != onlySecondaryEntityDescriptor) { - throw new IllegalArgumentException("The entitySelector (" + config.getEntitySelectorConfig() - + ")'s entityClass (" + (onlyEntityDescriptor == null ? null : onlyEntityDescriptor.getEntityClass()) - + ") and secondaryEntitySelectorConfig (" + config.getSecondaryEntitySelectorConfig() - + ")'s entityClass (" - + (onlySecondaryEntityDescriptor == null ? null : onlySecondaryEntityDescriptor.getEntityClass()) - + ") must be the same entity class."); + throw new IllegalArgumentException( + "The entitySelector (%s)'s entityClass (%s) and secondaryEntitySelectorConfig (%s)'s entityClass (%s) must be the same entity class." + .formatted(config.getEntitySelectorConfig(), + onlyEntityDescriptor == null ? null : onlyEntityDescriptor.getEntityClass(), + config.getSecondaryEntitySelectorConfig(), onlySecondaryEntityDescriptor == null ? null + : onlySecondaryEntityDescriptor.getEntityClass())); } } if (onlyEntityDescriptor != null) { - List> variableDescriptorList = - onlyEntityDescriptor.getGenuineVariableDescriptorList(); + var variableDescriptorList = onlyEntityDescriptor.getGenuineVariableDescriptorList(); // If there is a single list variable, unfold to list swap move selector config. if (variableDescriptorList.size() == 1 && variableDescriptorList.get(0).isListVariable()) { return buildListSwapMoveSelectorConfig(variableDescriptorList.get(0), true); } - // Otherwise, make sure there is no list variable, because SwapMove is not supposed to swap list variables. - failIfHasAnyGenuineListVariables(onlyEntityDescriptor); // No need for unfolding or deducing return null; } Collection> entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); - return buildUnfoldedMoveSelectorConfig(entityDescriptors); + return buildUnfoldedMoveSelectorConfig(configPolicy, entityDescriptors); } protected MoveSelectorConfig - buildUnfoldedMoveSelectorConfig(Collection> entityDescriptors) { - List moveSelectorConfigList = new ArrayList<>(entityDescriptors.size()); - - List> variableDescriptorList = - entityDescriptors.iterator().next().getGenuineVariableDescriptorList(); + buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy, + Collection> entityDescriptors) { + var moveSelectorConfigList = new ArrayList(entityDescriptors.size()); + + // When using a mixed model, + // we only fetch basic variables to avoiding creating a list move, + // and delegate it to the ListSwapMoveSelectorFactory. + // The strategy aims to provide a more normalized move selector collection for mixed models. + var variableDescriptorList = configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables() + ? entityDescriptors.iterator().next().getGenuineBasicVariableDescriptorList() + : entityDescriptors.iterator().next().getGenuineVariableDescriptorList(); // Only unfold into list swap move selector for the basic scenario with 1 entity and 1 list variable. if (entityDescriptors.size() == 1 && variableDescriptorList.size() == 1 && variableDescriptorList.get(0).isListVariable()) { // No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded() - ListSwapMoveSelectorConfig childMoveSelectorConfig = - buildListSwapMoveSelectorConfig(variableDescriptorList.get(0), false); + var childMoveSelectorConfig = buildListSwapMoveSelectorConfig(variableDescriptorList.get(0), false); moveSelectorConfigList.add(childMoveSelectorConfig); } else { // More complex scenarios do not support unfolding into list swap => fail fast if there is any list variable. - for (EntityDescriptor entityDescriptor : entityDescriptors) { - failIfHasAnyGenuineListVariables(entityDescriptor); - + for (var entityDescriptor : entityDescriptors) { + if (!entityDescriptor.hasAnyGenuineBasicVariables()) { + // We filter out entities that do not have basic variables (e.g., mixed models) + continue; + } // No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded() - SwapMoveSelectorConfig childMoveSelectorConfig = new SwapMoveSelectorConfig(); - EntitySelectorConfig childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig()); + var childMoveSelectorConfig = new SwapMoveSelectorConfig(); + var childEntitySelectorConfig = new EntitySelectorConfig(config.getEntitySelectorConfig()); if (childEntitySelectorConfig.getMimicSelectorRef() == null) { childEntitySelectorConfig.setEntityClass(entityDescriptor.getEntityClass()); } @@ -130,6 +129,9 @@ EntitySelectorFactory. create(config.getSecondaryEntitySelectorConfig moveSelectorConfigList.add(childMoveSelectorConfig); } } + if (moveSelectorConfigList.isEmpty()) { + throw new IllegalStateException("The swap move selector cannot be created when there is no basic variables."); + } MoveSelectorConfig unfoldedMoveSelectorConfig; if (moveSelectorConfigList.size() == 1) { @@ -141,17 +143,6 @@ EntitySelectorFactory. create(config.getSecondaryEntitySelectorConfig return unfoldedMoveSelectorConfig; } - private static void failIfHasAnyGenuineListVariables(EntityDescriptor entityDescriptor) { - if (entityDescriptor.hasAnyGenuineListVariables()) { - throw new IllegalArgumentException( - "The variableDescriptorList (" + entityDescriptor.getGenuineVariableDescriptorList() - + ") has multiple variables and one or more of them is a @" - + PlanningListVariable.class.getSimpleName() - + ", which is currently not supported."); - } - - } - private ListSwapMoveSelectorConfig buildListSwapMoveSelectorConfig(VariableDescriptor variableDescriptor, boolean inheritFoldedConfig) { LOGGER.warn( @@ -160,8 +151,8 @@ The swapMoveSelectorConfig ({}) is being used for a list variable. We are keeping this option through the 1.x release stream for backward compatibility reasons. Please update your solver config to use {} now.""", config, ListSwapMoveSelectorConfig.class.getSimpleName()); - ListSwapMoveSelectorConfig listSwapMoveSelectorConfig = new ListSwapMoveSelectorConfig(); - ValueSelectorConfig childValueSelectorConfig = new ValueSelectorConfig( + var listSwapMoveSelectorConfig = new ListSwapMoveSelectorConfig(); + var childValueSelectorConfig = new ValueSelectorConfig( new ValueSelectorConfig(variableDescriptor.getVariableName())); listSwapMoveSelectorConfig.setValueSelectorConfig(childValueSelectorConfig); if (inheritFoldedConfig) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactory.java index 991d33431e5..d450d8fc428 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactory.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -13,14 +12,12 @@ import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector; -import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory; public class ListSwapMoveSelectorFactory @@ -33,24 +30,24 @@ public ListSwapMoveSelectorFactory(ListSwapMoveSelectorConfig moveSelectorConfig @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - ValueSelectorConfig valueSelectorConfig = + var valueSelectorConfig = Objects.requireNonNullElseGet(config.getValueSelectorConfig(), ValueSelectorConfig::new); - ValueSelectorConfig secondaryValueSelectorConfig = + var secondaryValueSelectorConfig = Objects.requireNonNullElse(config.getSecondaryValueSelectorConfig(), valueSelectorConfig); - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - EntityDescriptor entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor()); - EntityIndependentValueSelector leftValueSelector = buildEntityIndependentValueSelector(configPolicy, + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var entityDescriptor = getTheOnlyEntityDescriptorWithListVariable(configPolicy.getSolutionDescriptor()); + var leftValueSelector = buildEntityIndependentValueSelector(configPolicy, entityDescriptor, valueSelectorConfig, minimumCacheType, selectionOrder); - EntityIndependentValueSelector rightValueSelector = buildEntityIndependentValueSelector(configPolicy, - entityDescriptor, secondaryValueSelectorConfig, minimumCacheType, selectionOrder); + var rightValueSelector = buildEntityIndependentValueSelector(configPolicy, entityDescriptor, + secondaryValueSelectorConfig, minimumCacheType, selectionOrder); - GenuineVariableDescriptor variableDescriptor = leftValueSelector.getVariableDescriptor(); + var variableDescriptor = leftValueSelector.getVariableDescriptor(); // This may be redundant but emphasizes that the ListSwapMove is not designed to swap elements // on multiple list variables, unlike the SwapMove, which swaps all (basic) variables between left and right entities. if (variableDescriptor != rightValueSelector.getVariableDescriptor()) { - throw new IllegalStateException("Impossible state: the leftValueSelector (" + leftValueSelector - + ") and the rightValueSelector (" + rightValueSelector - + ") have different variable descriptors. This should have failed fast during config unfolding."); + throw new IllegalStateException( + "Impossible state: the leftValueSelector (%s) and the rightValueSelector (%s) have different variable descriptors. This should have failed fast during config unfolding." + .formatted(leftValueSelector, rightValueSelector)); } return new ListSwapMoveSelector<>( @@ -65,13 +62,12 @@ private EntityIndependentValueSelector buildEntityIndependentValueSel ValueSelectorConfig valueSelectorConfig, SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder) { - ValueSelector valueSelector = ValueSelectorFactory. create(valueSelectorConfig) + var valueSelector = ValueSelectorFactory. create(valueSelectorConfig) .buildValueSelector(configPolicy, entityDescriptor, minimumCacheType, inheritedSelectionOrder); if (!(valueSelector instanceof EntityIndependentValueSelector)) { - throw new IllegalArgumentException("The listSwapMoveSelector (" + config - + ") for a list variable needs to be based on an " - + EntityIndependentValueSelector.class.getSimpleName() + " (" + valueSelector + ")." - + " Check your valueSelectorConfig."); + throw new IllegalArgumentException( + "The listSwapMoveSelector (%s) for a list variable needs to be based on an %s (%s). Check your valueSelectorConfig." + .formatted(config, EntityIndependentValueSelector.class.getSimpleName(), valueSelector)); } return (EntityIndependentValueSelector) valueSelector; @@ -79,63 +75,57 @@ private EntityIndependentValueSelector buildEntityIndependentValueSel @Override protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { - EntityDescriptor entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor()); - GenuineVariableDescriptor onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null + var entityDescriptor = getTheOnlyEntityDescriptorWithListVariable(configPolicy.getSolutionDescriptor()); + var onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null : ValueSelectorFactory. create(config.getValueSelectorConfig()) .extractVariableDescriptor(configPolicy, entityDescriptor); if (config.getSecondaryValueSelectorConfig() != null) { - GenuineVariableDescriptor onlySecondaryVariableDescriptor = + var onlySecondaryVariableDescriptor = ValueSelectorFactory. create(config.getSecondaryValueSelectorConfig()) .extractVariableDescriptor(configPolicy, entityDescriptor); if (onlyVariableDescriptor != onlySecondaryVariableDescriptor) { - throw new IllegalArgumentException("The valueSelector (" + config.getValueSelectorConfig() - + ")'s variableName (" - + (onlyVariableDescriptor == null ? null : onlyVariableDescriptor.getVariableName()) - + ") and secondaryValueSelectorConfig (" + config.getSecondaryValueSelectorConfig() - + ")'s variableName (" - + (onlySecondaryVariableDescriptor == null ? null : onlySecondaryVariableDescriptor.getVariableName()) - + ") must be the same planning list variable."); + throw new IllegalArgumentException( + "The valueSelector (%s)'s variableName (%s) and secondaryValueSelectorConfig (%s)'s variableName (%s) must be the same planning list variable." + .formatted(config.getValueSelectorConfig(), + onlyVariableDescriptor == null ? null : onlyVariableDescriptor.getVariableName(), + config.getSecondaryValueSelectorConfig(), onlySecondaryVariableDescriptor == null ? null + : onlySecondaryVariableDescriptor.getVariableName())); } } if (onlyVariableDescriptor != null) { if (!onlyVariableDescriptor.isListVariable()) { - throw new IllegalArgumentException("The listSwapMoveSelector (" + config - + ") is configured to use a planning variable (" + onlyVariableDescriptor - + "), which is not a planning list variable." - + " Either fix your annotations and use a @" + PlanningListVariable.class.getSimpleName() - + " on the variable to make it work with listSwapMoveSelector" - + " or use a swapMoveSelector instead."); + throw new IllegalArgumentException( + "Impossible state: the listSwapMoveSelector (%s) is configured to use a planning variable (%s), which is not a planning list variable. Either fix your annotations and use a @%s on the variable to make it work with listSwapMoveSelector or use a swapMoveSelector instead." + .formatted(config, onlyVariableDescriptor, PlanningListVariable.class.getSimpleName())); } // No need for unfolding or deducing return null; } - List> variableDescriptorList = - entityDescriptor.getGenuineVariableDescriptorList().stream() - .filter(VariableDescriptor::isListVariable) - .map(variableDescriptor -> ((ListVariableDescriptor) variableDescriptor)) - .collect(Collectors.toList()); + var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList().stream() + .filter(VariableDescriptor::isListVariable) + .map(variableDescriptor -> ((ListVariableDescriptor) variableDescriptor)) + .toList(); if (variableDescriptorList.isEmpty()) { - throw new IllegalArgumentException("The listSwapMoveSelector (" + config - + ") cannot unfold because there are no planning list variables for the only entity (" + entityDescriptor - + ") or no planning list variables at all."); + throw new IllegalArgumentException( + "Impossible state: the listSwapMoveSelector (%s) cannot unfold because there are no planning list variables for the only entity (%s) or no planning list variables at all." + .formatted(config, entityDescriptor)); } return buildUnfoldedMoveSelectorConfig(variableDescriptorList); } protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(List> variableDescriptorList) { - List moveSelectorConfigList = new ArrayList<>(variableDescriptorList.size()); - for (ListVariableDescriptor variableDescriptor : variableDescriptorList) { + var moveSelectorConfigList = new ArrayList(variableDescriptorList.size()); + for (var variableDescriptor : variableDescriptorList) { // No childMoveSelectorConfig.inherit() because of unfoldedMoveSelectorConfig.inheritFolded() - ListSwapMoveSelectorConfig childMoveSelectorConfig = new ListSwapMoveSelectorConfig(); - ValueSelectorConfig childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig()); + var childMoveSelectorConfig = new ListSwapMoveSelectorConfig(); + var childValueSelectorConfig = new ValueSelectorConfig(config.getValueSelectorConfig()); if (childValueSelectorConfig.getMimicSelectorRef() == null) { childValueSelectorConfig.setVariableName(variableDescriptor.getVariableName()); } childMoveSelectorConfig.setValueSelectorConfig(childValueSelectorConfig); if (config.getSecondaryValueSelectorConfig() != null) { - ValueSelectorConfig childSecondaryValueSelectorConfig = - new ValueSelectorConfig(config.getSecondaryValueSelectorConfig()); + var childSecondaryValueSelectorConfig = new ValueSelectorConfig(config.getSecondaryValueSelectorConfig()); if (childSecondaryValueSelectorConfig.getMimicSelectorRef() == null) { childSecondaryValueSelectorConfig.setVariableName(variableDescriptor.getVariableName()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java index 940832dcbd9..8462d340907 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java @@ -67,13 +67,18 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP if (onlyEntityDescriptor != null) { entityDescriptors = Collections.singletonList(onlyEntityDescriptor); } else { - entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); + // We select a single entity since there is only one descriptor that includes a list variable + var onlyEntityDescriptorWithListVariable = + getTheOnlyEntityDescriptorWithListVariable(configPolicy.getSolutionDescriptor()); + entityDescriptors = new ArrayList<>(); + if (onlyEntityDescriptorWithListVariable != null) { + entityDescriptors.add(onlyEntityDescriptorWithListVariable); + } } - if (entityDescriptors.size() > 1) { - throw new IllegalArgumentException(""" - The subListChangeMoveSelector (%s) cannot unfold when there are multiple entities (%s). - Please use one subListChangeMoveSelector per each planning list variable.""" - .formatted(config, entityDescriptors)); + if (entityDescriptors.isEmpty()) { + throw new IllegalArgumentException( + "The subListChangeMoveSelector (%s) cannot unfold because there are no planning list variables." + .formatted(config)); } var entityDescriptor = entityDescriptors.iterator().next(); @@ -117,11 +122,6 @@ The subListChangeMoveSelector (%s) cannot unfold when there are multiple entitie .map(variableDescriptor -> ((ListVariableDescriptor) variableDescriptor)) .toList()); } - if (variableDescriptorList.isEmpty()) { - throw new IllegalArgumentException( - "The subListChangeMoveSelector (%s) cannot unfold because there are no planning list variables." - .formatted(config)); - } if (variableDescriptorList.size() > 1) { throw new IllegalArgumentException( "The subListChangeMoveSelector (%s) cannot unfold because there are multiple planning list variables." diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactory.java index 13b56b110c0..3c9d459c569 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactory.java @@ -8,9 +8,7 @@ import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListSwapMoveSelectorConfig; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -30,15 +28,17 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy entitySelector = EntitySelectorFactory - . create(new EntitySelectorConfig()) + var onlyEntityDescriptor = getTheOnlyEntityDescriptorWithListVariable(configPolicy.getSolutionDescriptor()); + // We defined the entity class since there is a single entity descriptor that includes a list variable + var entitySelector = EntitySelectorFactory + . create(new EntitySelectorConfig().withEntityClass(onlyEntityDescriptor.getEntityClass())) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder); - SubListSelectorConfig subListSelectorConfig = + var subListSelectorConfig = Objects.requireNonNullElseGet(config.getSubListSelectorConfig(), SubListSelectorConfig::new); - SubListSelectorConfig secondarySubListSelectorConfig = + var secondarySubListSelectorConfig = Objects.requireNonNullElse(config.getSecondarySubListSelectorConfig(), subListSelectorConfig); // minimum -> subListSelector @@ -63,14 +63,14 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy leftSubListSelector = SubListSelectorFactory + var leftSubListSelector = SubListSelectorFactory . create(subListSelectorConfig) .buildSubListSelector(configPolicy, entitySelector, minimumCacheType, selectionOrder); - SubListSelector rightSubListSelector = SubListSelectorFactory + var rightSubListSelector = SubListSelectorFactory . create(secondarySubListSelectorConfig) .buildSubListSelector(configPolicy, entitySelector, minimumCacheType, selectionOrder); - boolean selectReversingMoveToo = Objects.requireNonNullElse(config.getSelectReversingMoveToo(), true); + var selectReversingMoveToo = Objects.requireNonNullElse(config.getSelectReversingMoveToo(), true); return new RandomSubListSwapMoveSelector<>(leftSubListSelector, rightSubListSelector, selectReversingMoveToo); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelectorFactory.java index f3c2f0b4234..96f1ca95f7b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveSelectorFactory.java @@ -9,7 +9,6 @@ import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -29,24 +28,29 @@ public KOptListMoveSelectorFactory(KOptListMoveSelectorConfig moveSelectorConfig @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { + var listVariableDescriptor = configPolicy.getSolutionDescriptor().getListVariableDescriptor(); + if (listVariableDescriptor == null) { + throw new IllegalArgumentException(""" + The kOptListMoveSelector (%s) can only be used when the domain model has a list variable. + Check your @%s and make sure it has a @%s.""" + .formatted(config, PlanningEntity.class.getSimpleName(), PlanningListVariable.class.getSimpleName())); + } + var originSelectorConfig = Objects.requireNonNullElseGet(config.getOriginSelectorConfig(), ValueSelectorConfig::new); var valueSelectorConfig = Objects.requireNonNullElseGet(config.getValueSelectorConfig(), ValueSelectorConfig::new); - var entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor()); - + var entityDescriptor = getTheOnlyEntityDescriptorWithListVariable(configPolicy.getSolutionDescriptor()); + if (originSelectorConfig.getVariableName() == null) { + originSelectorConfig.setVariableName(listVariableDescriptor.getVariableName()); + } + if (valueSelectorConfig.getVariableName() == null) { + valueSelectorConfig.setVariableName(listVariableDescriptor.getVariableName()); + } var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); var originSelector = buildEntityIndependentValueSelector(configPolicy, entityDescriptor, originSelectorConfig, minimumCacheType, selectionOrder); var valueSelector = buildEntityIndependentValueSelector(configPolicy, entityDescriptor, valueSelectorConfig, minimumCacheType, selectionOrder); - // TODO support coexistence of list and basic variables https://issues.redhat.com/browse/PLANNER-2755 - var variableDescriptor = getTheOnlyVariableDescriptor(entityDescriptor); - if (!variableDescriptor.isListVariable()) { - throw new IllegalArgumentException(""" - The kOptListMoveSelector (%s) can only be used when the domain model has a list variable. - Check your @%s and make sure it has a @%s.""" - .formatted(config, PlanningEntity.class.getSimpleName(), PlanningListVariable.class.getSimpleName())); - } int minimumK = Objects.requireNonNullElse(config.getMinimumK(), DEFAULT_MINIMUM_K); if (minimumK < 2) { @@ -71,8 +75,8 @@ The kOptListMoveSelector (%s) can only be used when the domain model has a list total = remainder; } pickedKDistribution[pickedKDistribution.length - 1] = total; - return new KOptListMoveSelector<>(((ListVariableDescriptor) variableDescriptor), - originSelector, valueSelector, minimumK, maximumK, pickedKDistribution); + return new KOptListMoveSelector<>(listVariableDescriptor, originSelector, valueSelector, minimumK, maximumK, + pickedKDistribution); } private EntityIndependentValueSelector buildEntityIndependentValueSelector( diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelectorFactory.java index b9386ddabd9..e33a645718d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/ListRuinRecreateMoveSelectorFactory.java @@ -30,19 +30,21 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy) ValueSelectorFactory. create(new ValueSelectorConfig()) + (EntityIndependentValueSelector) ValueSelectorFactory + . create( + new ValueSelectorConfig().withVariableName(listVariableDescriptor.getVariableName())) .buildValueSelector(configPolicy, entityDescriptor, minimumCacheType, SelectionOrder.RANDOM, false, ValueSelectorFactory.ListValueFilteringType.ACCEPT_ASSIGNED); var entityPlacerConfig = DefaultConstructionHeuristicPhaseFactory.buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor); - var constructionHeuristicPhaseConfig = new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(entityPlacerConfig); + var constructionHeuristicPhaseConfig = + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(entityPlacerConfig); var constructionHeuristicPhaseBuilder = RuinRecreateConstructionHeuristicPhaseBuilder.create(configPolicy, constructionHeuristicPhaseConfig); return new ListRuinRecreateMoveSelector<>(valueSelector, listVariableDescriptor, constructionHeuristicPhaseBuilder, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java index 4726ac294b0..0229c1284f2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java @@ -1,6 +1,8 @@ package ai.timefold.solver.core.impl.localsearch; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.domain.entity.PinningFilter; @@ -27,7 +29,6 @@ import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -68,10 +69,6 @@ public LocalSearchPhase buildPhase(int phaseIndex, boolean lastInitia The solver configuration enabled both move selectors and Move Streams. These are mutually exclusive features, please pick one or the other."""); } - if (solverConfigPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()) { - throw new UnsupportedOperationException( - "A mixed model using both basic and list variables is not supported yet."); - } var phaseConfigPolicy = solverConfigPolicy.createPhaseConfigPolicy(); var phaseTermination = buildPhaseTermination(phaseConfigPolicy, solverTermination); var decider = moveStreamsEnabled @@ -280,46 +277,31 @@ private SelectionOrder pickSelectionOrder() { private UnionMoveSelectorConfig determineDefaultMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { var solutionDescriptor = configPolicy.getSolutionDescriptor(); - var basicVariableDescriptorList = solutionDescriptor.getEntityDescriptors().stream() - .flatMap(entityDescriptor -> entityDescriptor.getGenuineVariableDescriptorList().stream()) - .filter(variableDescriptor -> !variableDescriptor.isListVariable()) - .distinct() - .toList(); - var hasChainedVariable = basicVariableDescriptorList.stream() - .filter(v -> v instanceof BasicVariableDescriptor) - .anyMatch(v -> ((BasicVariableDescriptor) v).isChained()); - var listVariableDescriptor = solutionDescriptor.getListVariableDescriptor(); - if (basicVariableDescriptorList.isEmpty()) { // We only have the one list variable. - return new UnionMoveSelectorConfig() - .withMoveSelectors(new ListChangeMoveSelectorConfig(), new ListSwapMoveSelectorConfig(), - new KOptListMoveSelectorConfig()); - } else if (listVariableDescriptor == null) { // We only have basic variables. - if (hasChainedVariable && basicVariableDescriptorList.size() == 1) { + if (solutionDescriptor.hasBothBasicAndListVariables()) { + var moveSelectorList = new ArrayList(); + // Specific basic variable moves + moveSelectorList.addAll(List.of(new ChangeMoveSelectorConfig(), new SwapMoveSelectorConfig())); + // Specific list variable moves + moveSelectorList.addAll(List.of(new ListChangeMoveSelectorConfig(), new ListSwapMoveSelectorConfig(), + new KOptListMoveSelectorConfig())); + return new UnionMoveSelectorConfig().withMoveSelectorList(moveSelectorList); + } else if (solutionDescriptor.hasListVariable()) { + // We only have the one list variable. + return new UnionMoveSelectorConfig().withMoveSelectors(new ListChangeMoveSelectorConfig(), + new ListSwapMoveSelectorConfig(), new KOptListMoveSelectorConfig()); + } else { + // We only have basic variables. + var basicVariableDescriptorList = solutionDescriptor.getBasicVariableDescriptorList(); + if (solutionDescriptor.hasChainedVariable() && basicVariableDescriptorList.size() == 1) { + // if there is only one chained variable, we add TailChainSwapMoveSelectorConfig return new UnionMoveSelectorConfig() .withMoveSelectors(new ChangeMoveSelectorConfig(), new SwapMoveSelectorConfig(), new TailChainSwapMoveSelectorConfig()); } else { - return new UnionMoveSelectorConfig() - .withMoveSelectors(new ChangeMoveSelectorConfig(), new SwapMoveSelectorConfig()); - } - } else { - /* - * We have a mix of basic and list variables. - * The "old" move selector configs handle this situation correctly but they complain of it being deprecated. - * - * Combining essential variables with list variables in a single entity is not supported. Therefore, - * default selectors do not support enabling Nearby Selection with multiple entities. - */ - if (configPolicy.getNearbyDistanceMeterClass() != null) { - throw new IllegalArgumentException(""" - The configuration contains both basic and list variables, \ - which makes it incompatible with using a top-level nearbyDistanceMeterClass (%s). - Specify move selectors manually or \ - remove the top-level nearbyDistanceMeterClass from your solver config.""" - .formatted(configPolicy.getNearbyDistanceMeterClass())); + // Basic variables or a mixed model with basic and chained variables + return new UnionMoveSelectorConfig().withMoveSelectors(new ChangeMoveSelectorConfig(), + new SwapMoveSelectorConfig()); } - return new UnionMoveSelectorConfig() - .withMoveSelectors(new ChangeMoveSelectorConfig(), new SwapMoveSelectorConfig()); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java index 4d406377dd6..fe3be6486ec 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java @@ -5,6 +5,7 @@ import ai.timefold.solver.core.impl.constructionheuristic.placer.EntityPlacer; import ai.timefold.solver.core.impl.constructionheuristic.placer.Placement; +import ai.timefold.solver.core.impl.constructionheuristic.placer.QueuedValuePlacer; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; @@ -80,4 +81,13 @@ public boolean hasNext() { return Objects.requireNonNull(placementIterator).hasNext(); } + public boolean hasListVariable() { + // When the placer depends on a list variable, the CH phase creates a ListChangeMoveSelector. + // However, in certain cases, such as ALLOCATE_TO_VALUE_FROM_QUEUE, + // a QueuedValuePlacer can be created for a basic planning variable, + // and in these cases, the move selector does not rely on a list variable. + return placer instanceof QueuedValuePlacer queuedValuePlacer + && queuedValuePlacer.hasListChangeMoveSelector(); + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java index 935319ed64a..a5c7a5265f0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java @@ -14,7 +14,6 @@ import ai.timefold.solver.core.api.solver.SolverConfigOverride; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; -import ai.timefold.solver.core.config.constructionheuristic.placer.EntityPlacerConfig; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; @@ -27,8 +26,8 @@ import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.AbstractFromConfigFactory; import ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhaseFactory; +import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; 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; import ai.timefold.solver.core.impl.phase.Phase; import ai.timefold.solver.core.impl.phase.PhaseFactory; @@ -131,7 +130,7 @@ public > ScoreDirectorFactory ge var previewFeaturesEnabled = solverConfig.getEnablePreviewFeatureSet(); var configPolicy = new HeuristicConfigPolicy.Builder() - .withPreviewFeatureList(previewFeaturesEnabled) + .withPreviewFeatureSet(previewFeaturesEnabled) .withEnvironmentMode(environmentMode) .withMoveThreadCount(moveThreadCount) .withMoveThreadBufferSize(solverConfig.getMoveThreadBufferSize()) @@ -222,42 +221,47 @@ public List> buildPhaseList(HeuristicConfigPolicy co var phaseConfigList = solverConfig.getPhaseConfigList(); if (ConfigUtils.isEmptyCollection(phaseConfigList)) { var genuineEntityDescriptorCollection = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); - var listVariableDescriptor = configPolicy.getSolutionDescriptor().getListVariableDescriptor(); - var entityClassToListVariableDescriptorListMap = - listVariableDescriptor == null ? Collections., List>> emptyMap() - : Collections.singletonMap(listVariableDescriptor.getEntityDescriptor().getEntityClass(), - List.of(listVariableDescriptor)); - - phaseConfigList = new ArrayList<>(genuineEntityDescriptorCollection.size() + 1); - for (var genuineEntityDescriptor : genuineEntityDescriptorCollection) { - var constructionHeuristicPhaseConfig = new ConstructionHeuristicPhaseConfig(); - EntityPlacerConfig entityPlacerConfig; - - if (entityClassToListVariableDescriptorListMap.containsKey(genuineEntityDescriptor.getEntityClass())) { - var listVariableDescriptorList = - entityClassToListVariableDescriptorListMap.get(genuineEntityDescriptor.getEntityClass()); - if (listVariableDescriptorList.size() != 1) { - // TODO: Do multiple Construction Heuristics for each list variable descriptor? - throw new IllegalArgumentException( - "Construction Heuristic phase does not support multiple list variables (%s) for planning entity (%s)." - .formatted(listVariableDescriptorList, genuineEntityDescriptor.getEntityClass())); - } - entityPlacerConfig = - DefaultConstructionHeuristicPhaseFactory.buildListVariableQueuedValuePlacerConfig(configPolicy, - listVariableDescriptorList.get(0)); + phaseConfigList = new ArrayList<>(genuineEntityDescriptorCollection.size() + 2); + for (var entityDescriptor : genuineEntityDescriptorCollection) { + if (entityDescriptor.hasBothGenuineListAndBasicVariables()) { + // We add a separate step for each variable type + phaseConfigList.add(buildConstructionHeuristicPhaseConfigForBasicVariable(configPolicy, entityDescriptor)); + phaseConfigList.add(buildConstructionHeuristicPhaseConfigForListVariable(configPolicy, entityDescriptor)); + } else if (entityDescriptor.hasAnyGenuineListVariables()) { + // There is no need to revalidate the number of list variables, + // as it has already been validated in SolutionDescriptor + // TODO: Do multiple Construction Heuristics for each list variable descriptor? + phaseConfigList.add(buildConstructionHeuristicPhaseConfigForListVariable(configPolicy, entityDescriptor)); } else { - entityPlacerConfig = new QueuedEntityPlacerConfig().withEntitySelectorConfig(AbstractFromConfigFactory - .getDefaultEntitySelectorConfigForEntity(configPolicy, genuineEntityDescriptor)); + phaseConfigList.add(buildConstructionHeuristicPhaseConfigForBasicVariable(configPolicy, entityDescriptor)); } - - constructionHeuristicPhaseConfig.setEntityPlacerConfigList(List.of(entityPlacerConfig)); - phaseConfigList.add(constructionHeuristicPhaseConfig); } phaseConfigList.add(new LocalSearchPhaseConfig()); } return PhaseFactory.buildPhases(phaseConfigList, configPolicy, bestSolutionRecaller, termination); } + private ConstructionHeuristicPhaseConfig + buildConstructionHeuristicPhaseConfigForBasicVariable(HeuristicConfigPolicy configPolicy, + EntityDescriptor entityDescriptor) { + var constructionHeuristicPhaseConfig = new ConstructionHeuristicPhaseConfig(); + constructionHeuristicPhaseConfig + .setEntityPlacerConfig(new QueuedEntityPlacerConfig().withEntitySelectorConfig(AbstractFromConfigFactory + .getDefaultEntitySelectorConfigForEntity(configPolicy, entityDescriptor))); + return constructionHeuristicPhaseConfig; + } + + private ConstructionHeuristicPhaseConfig + buildConstructionHeuristicPhaseConfigForListVariable(HeuristicConfigPolicy configPolicy, + EntityDescriptor entityDescriptor) { + var constructionHeuristicPhaseConfig = new ConstructionHeuristicPhaseConfig(); + var listVariableDescriptor = entityDescriptor.getGenuineListVariableDescriptor(); + constructionHeuristicPhaseConfig + .setEntityPlacerConfig(DefaultConstructionHeuristicPhaseFactory + .buildListVariableQueuedValuePlacerConfig(configPolicy, listVariableDescriptor)); + return constructionHeuristicPhaseConfig; + } + public void ensurePreviewFeature(PreviewFeature previewFeature) { HeuristicConfigPolicy.ensurePreviewFeature(previewFeature, solverConfig.getEnablePreviewFeatureSet()); } diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index b3fe9064749..74067637912 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -255,7 +255,7 @@ - + diff --git a/core/src/test/java/ai/timefold/solver/core/api/solver/SolutionManagerTest.java b/core/src/test/java/ai/timefold/solver/core/api/solver/SolutionManagerTest.java index fb32a936fa3..dfa7e87909e 100644 --- a/core/src/test/java/ai/timefold/solver/core/api/solver/SolutionManagerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/api/solver/SolutionManagerTest.java @@ -31,10 +31,10 @@ import ai.timefold.solver.core.testdomain.list.shadowhistory.TestdataListSolutionWithShadowHistory; import ai.timefold.solver.core.testdomain.list.shadowhistory.TestdataListValueWithShadowHistory; import ai.timefold.solver.core.testdomain.list.shadowhistory.TestdataListWithShadowHistoryIncrementalScoreCalculator; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarSolution; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultivarIncrementalScoreCalculator; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataOtherValue; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultivarIncrementalScoreCalculator; +import ai.timefold.solver.core.testdomain.multivar.TestdataOtherValue; import ai.timefold.solver.core.testdomain.shadow.TestdataShadowedEntity; import ai.timefold.solver.core.testdomain.shadow.TestdataShadowedIncrementalScoreCalculator; import ai.timefold.solver.core.testdomain.shadow.TestdataShadowedSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java index d03a4f6ed55..bc8ba73f3f1 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java @@ -166,7 +166,7 @@ void withConstraintProviderClass() { } @Test - void withEnablePreviewFeatureList() { + void withEnablePreviewFeatureSet() { var solverConfig = new SolverConfig(); assertThat(solverConfig.getEnablePreviewFeatureSet()).isNull(); solverConfig.withPreviewFeature(PreviewFeature.DIVERSIFIED_LATE_ACCEPTANCE); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java index cbc6bff7f2f..f904fb90460 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java @@ -1,5 +1,6 @@ package ai.timefold.solver.core.impl.constructionheuristic; +import static ai.timefold.solver.core.config.solver.PreviewFeature.DECLARATIVE_SHADOW_VARIABLES; import static ai.timefold.solver.core.testutil.PlannerAssert.assertCode; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -17,15 +18,8 @@ import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; -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.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig; -import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; -import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig; import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; -import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.solver.DefaultSolver; import ai.timefold.solver.core.impl.solver.scope.SolverScope; @@ -35,23 +29,14 @@ import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; -import ai.timefold.solver.core.testdomain.list.TestdataListVarEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEntity; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListSolution; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListValue; -import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntityEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntityFirstEntity; -import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntitySecondEntity; -import ai.timefold.solver.core.testdomain.multivar.list.multientity.TestdataListMultiEntitySolution; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarOtherValue; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarSolution; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarValue; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar.TestdataUnassignedListMultiVarEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar.TestdataUnassignedListMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar.TestdataUnassignedListMultiVarSolution; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedEntity; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedOtherValue; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedSolution; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedValue; import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedEntity; import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity; @@ -364,274 +349,14 @@ void constructionHeuristicAllocateToValueFromQueue() { } @Test - void failWithExceededMultipleQueuedEntityPlacers() { - var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(new QueuedEntityPlacerConfig(), new QueuedEntityPlacerConfig(), - new QueuedEntityPlacerConfig())); - - assertThatCode(() -> PlannerTestUtils.solve(solverConfig, new TestdataSolution("s1"))) - .hasMessageContaining( - "The Construction Heuristic configuration (ConstructionHeuristicPhaseConfig) only support a maximum of two entity placers."); - } - - @Test - void failWithMultipleQueuedEntityPlacers() { - var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(new QueuedEntityPlacerConfig(), new QueuedEntityPlacerConfig())); - - assertThatCode(() -> PlannerTestUtils.solve(solverConfig, new TestdataSolution("s1"))) - .hasMessageContaining( - "The Construction Heuristic configuration (ConstructionHeuristicPhaseConfig) cannot contain duplicate placer configurations.") - .hasMessageContaining("Maybe define multiple move selectors if there are more than one basic variables"); - - var solverConfig2 = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(new QueuedValuePlacerConfig(), new QueuedValuePlacerConfig())); - - assertThatCode(() -> PlannerTestUtils.solve(solverConfig2, new TestdataSolution("s1"))) - .hasMessageContaining( - "The Construction Heuristic configuration (ConstructionHeuristicPhaseConfig) cannot contain duplicate placer configurations."); - } - - @Test - void failWithPooledEntityPlacers() { - var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(new QueuedEntityPlacerConfig(), new PooledEntityPlacerConfig())); + void failMixedModelDefaultConfiguration() { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES); assertThatCode(() -> PlannerTestUtils.solve(solverConfig, new TestdataSolution("s1"))) .hasMessageContaining( - "The Construction Heuristic configuration (ConstructionHeuristicPhaseConfig) does not support multiple configurations when using the pooled placer configuration PooledEntityPlacerConfig."); - } - - @Test - void failMultiEntityWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiEntitySolution.class, TestdataListMultiEntityFirstEntity.class, - TestdataListMultiEntitySecondEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class); - - var problem = TestdataListMultiEntitySolution.generateUninitializedSolution(2, 2, 2); - assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) - .hasMessageContaining("has no entityClass configured and because there are multiple in the entityClassSet") - .hasMessageContaining("it cannot be deduced automatically"); + "has both basic and list variables and cannot be deduced automatically"); } - - @Test - void failLocalSearchWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class, - TestdataListMultiVarOtherValue.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16)), - new LocalSearchPhaseConfig()) - .withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) - .hasMessageContaining("A mixed model using both basic and list variables is not supported yet."); - } - - @Test - void solveWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class, - TestdataListMultiVarOtherValue.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) - .isEmpty(); - } - - @Test - void solvePinnedWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class, - TestdataListMultiVarOtherValue.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - // Pin the first entity - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setPinnedIndex(0); - var solution = PlannerTestUtils.solve(solverConfig, problem); - // The first entity should remain unchanged - assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getValueList()).isEmpty(); - } - - @Test - void solveUnassignedWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataUnassignedListMultiVarSolution.class, TestdataUnassignedListMultiVarEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataUnassignedListMultiVarEasyScoreCalculator.class); - - var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - // Block values and make the basic and list variables unassigned - problem.getValueList().get(0).setBlocked(true); - problem.getValueList().get(1).setBlocked(true); - problem.getOtherValueList().get(0).setBlocked(true); - problem.getOtherValueList().get(1).setBlocked(true); - var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getBasicValue() == null)) - .hasSize(2); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getSecondBasicValue() != null)) - .hasSize(2); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getValueList().isEmpty())) - .hasSize(2); - } - - @Test - void solvePinnedAndUnassignedWithListAndBasicVariables() { - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataUnassignedListMultiVarSolution.class, TestdataUnassignedListMultiVarEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataUnassignedListMultiVarEasyScoreCalculator.class); - - // Pin the entire first entity - var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); - // Block values and make the basic and list variables unassigned - problem.getValueList().get(0).setBlocked(true); - problem.getValueList().get(1).setBlocked(true); - problem.getOtherValueList().get(0).setBlocked(true); - problem.getOtherValueList().get(1).setBlocked(true); - var solution = PlannerTestUtils.solve(solverConfig, problem); - // The first entity should remain unchanged - assertThat(solution.getEntityList().get(0).getBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(0).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(1).getValueList()).isEmpty(); - - // Pin partially the first entity list - problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 4, 2); - problem.getEntityList().get(0).setPinnedIndex(2); - problem.getEntityList().get(0).setValueList(problem.getValueList().subList(1, 3)); - // Block values and make the basic variable unassigned - problem.getOtherValueList().get(0).setBlocked(true); - problem.getOtherValueList().get(1).setBlocked(true); - solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); - // The pinning index fixed the values 1 and 2. The only remaining option is values are 0 and 3. - // The score is bigger when the list size is 3 - assertThat(solution.getEntityList().get(0).getValueList()).hasSize(3); - assertThat(solution.getEntityList().get(0).getValueList()) - .hasSameElementsAs( - List.of(problem.getValueList().get(1), problem.getValueList().get(2), problem.getValueList().get(0))); - assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); - assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); - assertThat(solution.getEntityList().get(1).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(1).getValueList()).hasSameElementsAs(List.of(problem.getValueList().get(3))); - } - - @Test - void solveCustomConfigurationMultiEntityWithListAndBasicVariables() { - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)) - .withEntityClass(TestdataListMultiEntityFirstEntity.class); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiEntitySolution.class, TestdataListMultiEntityFirstEntity.class, - TestdataListMultiEntitySecondEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(valuePlacerConfig, entityPlacerConfig) - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListMultiEntityEasyScoreCalculator.class); - - var problem = TestdataListMultiEntitySolution.generateUninitializedSolution(2, 2, 2); - var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getValueList().isEmpty())) - .isEmpty(); - assertThat(solution.getOtherEntityList().stream() - .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null)) - .isEmpty(); - } - - @Test - void solveCustomConfigurationWithListAndBasicVariables() { - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, TestdataListMultiVarValue.class, - TestdataListMultiVarOtherValue.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(valuePlacerConfig, entityPlacerConfig) - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListMultiVarEasyScoreCalculator.class); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) - .isEmpty(); - } - - @Test - void solveCustomConfigurationWithListVariables() { - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - - var solverConfig = PlannerTestUtils.buildSolverConfig( - TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfigList(valuePlacerConfig) - .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) - .withEasyScoreCalculatorClass(TestdataListVarEasyScoreCalculator.class); - - var problem = TestdataListSolution.generateUninitializedSolution(2, 2); - var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getValueList().isEmpty())) - .isEmpty(); - } - } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerFactoryTest.java index 8b67c05d4ab..57b58585853 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerFactoryTest.java @@ -27,8 +27,8 @@ import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.difficultyweight.TestdataDifficultyWeightSolution; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarSolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerTest.java index eb5fc2d56e7..dcd93980d16 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerTest.java @@ -33,8 +33,8 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarSolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedMultiplePlacerFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedMultiplePlacerFactoryTest.java deleted file mode 100644 index c636c58a4e8..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedMultiplePlacerFactoryTest.java +++ /dev/null @@ -1,476 +0,0 @@ -package ai.timefold.solver.core.impl.constructionheuristic.placer.entity; - -import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE; -import static ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE; -import static ai.timefold.solver.core.config.score.trend.InitializingScoreTrendLevel.ANY; -import static ai.timefold.solver.core.config.solver.EnvironmentMode.PHASE_ASSERT; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Random; - -import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig; -import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig; -import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig; -import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; -import ai.timefold.solver.core.config.score.trend.InitializingScoreTrendLevel; -import ai.timefold.solver.core.impl.constructionheuristic.placer.EntityPlacerFactory; -import ai.timefold.solver.core.impl.constructionheuristic.placer.internal.QueuedMultiplePlacerConfig; -import ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport; -import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; -import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend; -import ai.timefold.solver.core.impl.solver.scope.SolverScope; -import ai.timefold.solver.core.impl.util.MutableInt; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarSolution; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar.TestdataUnassignedListMultiVarSolution; - -import org.junit.jupiter.api.Test; - -class QueuedMultiplePlacerFactoryTest { - - @Test - void testPlacersForConstructionHeuristic() { - var solutionDescriptor = TestdataListMultiVarSolution.buildSolutionDescriptor(); - var configPolicy = new HeuristicConfigPolicy.Builder() - .withEnvironmentMode(PHASE_ASSERT) - .withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY })) - .withSolutionDescriptor(solutionDescriptor) - .withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE) - .withReinitializeVariableFilterEnabled(true) - .withInitializedChainedValueFilterEnabled(true) - .withUnassignedValuesAllowed(true) - .withRandom(new Random(0)) - .build(); - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - var placerConfig = new QueuedMultiplePlacerConfig() - .withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig)); - var placer = EntityPlacerFactory. create(placerConfig).buildEntityPlacer(configPolicy); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - var solverScope = mock(SolverScope.class); - var scoreDirector = mock(InnerScoreDirector.class); - var random = new Random(0L); - when(solverScope.getScoreDirector()).thenReturn(scoreDirector); - when(solverScope.getWorkingRandom()).thenReturn(random); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); - - var supplyManager = VariableListenerSupport.create(scoreDirector); - when(scoreDirector.getSupplyManager()).thenReturn(supplyManager); - supplyManager.linkVariableListeners(); - supplyManager.resetWorkingSolution(); - - placer.solvingStarted(solverScope); - var phaseScope = mock(AbstractPhaseScope.class); - when(phaseScope.getScoreDirector()).thenReturn(scoreDirector); - placer.phaseStarted(phaseScope); - - var placerIterator = placer.iterator(); - - // Step 1 - // 1 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 2 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 3 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 4 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 5 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 6 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 7 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 8 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 9 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 10 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 11 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 12 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 13 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 14 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 15 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 16 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - assertThat(placerIterator.hasNext()).isTrue(); - var counter = new MutableInt(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(16); - - // Accept the move -> 1 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); - problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - var stepScope = mock(AbstractStepScope.class); - placer.stepEnded(stepScope); - - // Step 2 - // 1 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 2 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 3 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 4 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 5 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 6 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 7 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 8 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 9 = Generated Value 1 -> Entity 0[1] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 10 = Generated Value 1 -> Entity 0[1] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 11 = Generated Value 1 -> Entity 0[1] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 12 = Generated Value 1 -> Entity 0[1] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - counter.setValue(0); - assertThat(placerIterator.hasNext()).isTrue(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(12); - - // Accept the move = 5 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(1).setValueList(List.of(problem.getValueList().get(1))); - problem.getEntityList().get(1).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - placer.stepEnded(stepScope); - // No more placements - assertThat(placerIterator.hasNext()).isFalse(); - } - - @Test - void testPinnedPlacersForConstructionHeuristic() { - var solutionDescriptor = TestdataListMultiVarSolution.buildSolutionDescriptor(); - var configPolicy = new HeuristicConfigPolicy.Builder() - .withEnvironmentMode(PHASE_ASSERT) - .withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY })) - .withSolutionDescriptor(solutionDescriptor) - .withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE) - .withReinitializeVariableFilterEnabled(true) - .withInitializedChainedValueFilterEnabled(true) - .withUnassignedValuesAllowed(true) - .withRandom(new Random(0)) - .build(); - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - var placerConfig = new QueuedMultiplePlacerConfig() - .withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig)); - var placer = EntityPlacerFactory. create(placerConfig).buildEntityPlacer(configPolicy); - - var problem = TestdataListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - // Pin the first entity - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setPinnedIndex(2); - problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); - - var solverScope = mock(SolverScope.class); - var scoreDirector = mock(InnerScoreDirector.class); - var random = new Random(0L); - when(solverScope.getScoreDirector()).thenReturn(scoreDirector); - when(solverScope.getWorkingRandom()).thenReturn(random); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); - - var supplyManager = VariableListenerSupport.create(scoreDirector); - when(scoreDirector.getSupplyManager()).thenReturn(supplyManager); - supplyManager.linkVariableListeners(); - supplyManager.resetWorkingSolution(); - - placer.solvingStarted(solverScope); - var phaseScope = mock(AbstractPhaseScope.class); - when(phaseScope.getScoreDirector()).thenReturn(scoreDirector); - placer.phaseStarted(phaseScope); - - var placerIterator = placer.iterator(); - - // Step 1 - // 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 2 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 3 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 4 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - assertThat(placerIterator.hasNext()).isTrue(); - var counter = new MutableInt(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(4); - - // Accept the move -> 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(1).setValueList(List.of(problem.getValueList().get(1))); - problem.getEntityList().get(1).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - var stepScope = mock(AbstractStepScope.class); - placer.stepEnded(stepScope); - assertThat(placerIterator.hasNext()).isFalse(); - } - - @Test - void testUnassignedPlacersForConstructionHeuristic() { - var solutionDescriptor = TestdataUnassignedListMultiVarSolution.buildSolutionDescriptor(); - var configPolicy = new HeuristicConfigPolicy.Builder() - .withEnvironmentMode(PHASE_ASSERT) - .withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY })) - .withSolutionDescriptor(solutionDescriptor) - .withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE) - .withReinitializeVariableFilterEnabled(true) - .withInitializedChainedValueFilterEnabled(true) - .withUnassignedValuesAllowed(true) - .withRandom(new Random(0)) - .build(); - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - var placerConfig = new QueuedMultiplePlacerConfig() - .withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig)); - var placer = EntityPlacerFactory. create(placerConfig) - .buildEntityPlacer(configPolicy); - - var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - var solverScope = mock(SolverScope.class); - var scoreDirector = mock(InnerScoreDirector.class); - var random = new Random(0L); - when(solverScope.getScoreDirector()).thenReturn(scoreDirector); - when(solverScope.getWorkingRandom()).thenReturn(random); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); - - var supplyManager = VariableListenerSupport.create(scoreDirector); - when(scoreDirector.getSupplyManager()).thenReturn(supplyManager); - supplyManager.linkVariableListeners(); - supplyManager.resetWorkingSolution(); - - placer.solvingStarted(solverScope); - var phaseScope = mock(AbstractPhaseScope.class); - when(phaseScope.getScoreDirector()).thenReturn(scoreDirector); - placer.phaseStarted(phaseScope); - - var placerIterator = placer.iterator(); - - // Step 1 - // 1 = Generated Value 0 -> Entity 0[0] - Entity 0 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 2 = Generated Value 0 -> Entity 0[0] - Entity 0 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 3 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 4 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 5 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 6 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 7 = Generated Value 0 -> Entity 0[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 8 = Generated Value 0 -> Entity 0[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 9 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 10 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 11 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 12 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 13 = Generated Value 0 -> Entity 1[0] - Entity 0 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 14 = Generated Value 0 -> Entity 1[0] - Entity 0 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 15 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 16 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 17 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 18 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 19 = Generated Value 0 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 20 = Generated Value 0 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 21 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 22 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 23 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 24 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 25 = NoChange - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 26 = NoChange - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 27 = NoChange - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 28 = NoChange - Entity 0 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 29 = NoChange - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 30 = NoChange - Entity 0 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 31 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 32 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 33 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 34 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 35 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 36 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - assertThat(placerIterator.hasNext()).isTrue(); - var counter = new MutableInt(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(36); - - // Accept the move - 31 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - var stepScope = mock(AbstractStepScope.class); - placer.stepEnded(stepScope); - - // Step 2 - // 1 = Generated Value 1 -> Entity 0[0] - Entity 0 - null -> basicValue - // 2 = Generated Value 1 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - // 3 = Generated Value 1 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - // 4 = Generated Value 1 -> Entity 0[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 5 = Generated Value 1 -> Entity 0[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 6 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 7 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 8 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 9 = Generated Value 1 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 10 = Generated Value 1 -> Entity 1[0] - Entity 0 - null -> basicValue - // 11 = Generated Value 1 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - // 12 = Generated Value 1 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - // 13 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 14 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 15 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 16 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 17 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 18 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 19 = NoChange - Entity 0 - null -> basicValue - // 20 = NoChange - Entity 0 - Generated Other Value 0 -> basicValue - // 21 = NoChange - Entity 0 - Generated Other Value 1 -> basicValue - // 22 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 23 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 24 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 25 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 26 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 27 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - counter.setValue(0); - assertThat(placerIterator.hasNext()).isTrue(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(27); - - // Accept the move - 22 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - placer.stepEnded(stepScope); - // 1 = Generated Value 0 -> Entity 0[0] - Entity 0 - null -> basicValue - // 2 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 0 -> basicValue - // 3 = Generated Value 0 -> Entity 0[0] - Entity 0 - Generated Other Value 1 -> basicValue - // 4 = Generated Value 0 -> Entity 0[0] - Entity 1 - null -> basicValue - // 5 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 0 -> basicValue - // 6 = Generated Value 0 -> Entity 0[0] - Entity 1 - Generated Other Value 1 -> basicValue - // 7 = Generated Value 0 -> Entity 1[0] - Entity 0 - null -> basicValue - // 8 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 0 -> basicValue - // 9 = Generated Value 0 -> Entity 1[0] - Entity 0 - Generated Other Value 1 -> basicValue - // 10 = Generated Value 0 -> Entity 1[0] - Entity 1 - null -> basicValue - // 11 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - // 12 = Generated Value 0 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - // 13 = NoChange - Entity 0 - null -> basicValue - // 14 = NoChange - Entity 0 - Generated Other Value 0 -> basicValue - // 15 = NoChange - Entity 0 - Generated Other Value 1 -> basicValue - // 16 = NoChange - Entity 1 - null -> basicValue - // 17 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - // 18 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - counter.setValue(0); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(18); - } - - @Test - void testPinnedUnassignedPlacersForConstructionHeuristic() { - var solutionDescriptor = TestdataUnassignedListMultiVarSolution.buildSolutionDescriptor(); - var configPolicy = new HeuristicConfigPolicy.Builder() - .withEnvironmentMode(PHASE_ASSERT) - .withInitializingScoreTrend(new InitializingScoreTrend(new InitializingScoreTrendLevel[] { ANY })) - .withSolutionDescriptor(solutionDescriptor) - .withEntitySorterManner(DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(INCREASING_STRENGTH_IF_AVAILABLE) - .withReinitializeVariableFilterEnabled(true) - .withInitializedChainedValueFilterEnabled(true) - .withUnassignedValuesAllowed(true) - .withRandom(new Random(0)) - .build(); - var valueSelectorConfig = new ValueSelectorConfig("valueList") - .withId("valueList"); - var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() - .withMimicSelectorRef("valueList") - .withVariableName("valueList"); - var valuePlacerConfig = new QueuedValuePlacerConfig() - .withValueSelectorConfig(valueSelectorConfig) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); - var entityPlacerConfig = new QueuedEntityPlacerConfig(); - var placerConfig = new QueuedMultiplePlacerConfig() - .withPlacerConfigList(List.of(valuePlacerConfig, entityPlacerConfig)); - var placer = EntityPlacerFactory. create(placerConfig) - .buildEntityPlacer(configPolicy); - - var problem = TestdataUnassignedListMultiVarSolution.generateUninitializedSolution(2, 2, 2); - // Pin the first entity - problem.getEntityList().get(0).setPinned(true); - problem.getEntityList().get(0).setPinnedIndex(2); - problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); - problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); - - var solverScope = mock(SolverScope.class); - var scoreDirector = mock(InnerScoreDirector.class); - var random = new Random(0L); - when(solverScope.getScoreDirector()).thenReturn(scoreDirector); - when(solverScope.getWorkingRandom()).thenReturn(random); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getWorkingSolution()).thenReturn(problem); - when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor); - - var supplyManager = VariableListenerSupport.create(scoreDirector); - when(scoreDirector.getSupplyManager()).thenReturn(supplyManager); - supplyManager.linkVariableListeners(); - supplyManager.resetWorkingSolution(); - - placer.solvingStarted(solverScope); - var phaseScope = mock(AbstractPhaseScope.class); - when(phaseScope.getScoreDirector()).thenReturn(scoreDirector); - placer.phaseStarted(phaseScope); - - var placerIterator = placer.iterator(); - // Step 1 - // 1 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 2 = Generated Value 1 -> Entity 1[0] - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 3 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 4 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 5 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 6 = Generated Value 1 -> Entity 1[0] - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 7 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - // 8 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 1 -> secondBasicValue - // 9 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 10 = NoChange - Entity 1 - Generated Other Value 0 -> basicValue - Generated Other Value 1 -> secondBasicValue - // 11 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 0 -> secondBasicValue - // 12 = NoChange - Entity 1 - Generated Other Value 1 -> basicValue - Generated Other Value 1 -> secondBasicValue - assertThat(placerIterator.hasNext()).isTrue(); - var counter = new MutableInt(); - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(12); - - // Accept the move - 7 = NoChange - Entity 1 - null -> basicValue - Generated Other Value 0 -> secondBasicValue - problem.getEntityList().get(1).setSecondBasicValue(problem.getOtherValueList().get(0)); - // Update all variables - supplyManager.resetWorkingSolution(); - var stepScope = mock(AbstractStepScope.class); - placer.stepEnded(stepScope); - counter.setValue(0); - // 1 = Generated Value 1 -> Entity 1[0] - null -> secondBasicValue - // 2 = Generated Value 1 -> Entity 1[0] - Generated Other Value 0 -> secondBasicValue - // 3 = Generated Value 1 -> Entity 1[0] - Generated Other Value 1 -> secondBasicValue - // 4 = NoChange - null -> secondBasicValue - // 5 = NoChange - Generated Other Value 0 -> secondBasicValue - // 6 = NoChange - Generated Other Value 1 -> secondBasicValue - placerIterator.next().iterator().forEachRemaining(move -> counter.increment()); - assertThat(counter.intValue()).isEqualTo(6); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java index 125cece9919..f0cde221813 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/CartesianProductMoveSelectorTest.java @@ -24,7 +24,7 @@ import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactoryTest.java index 263b2e7af3c..9597800e068 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactoryTest.java @@ -24,7 +24,7 @@ import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.multientity.TestdataHerdEntity; import ai.timefold.solver.core.testdomain.multientity.TestdataMultiEntitySolution; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarSolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java index 565cda0bad6..10158a9ceeb 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java @@ -13,8 +13,8 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataOtherValue; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataOtherValue; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java index 0947f592cbd..51224744b6b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java @@ -15,7 +15,7 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java index 051cf917448..df0a1903580 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java @@ -14,7 +14,7 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactoryTest.java index f751942b036..e35b4df3894 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactoryTest.java @@ -21,7 +21,7 @@ import ai.timefold.solver.core.testdomain.multientity.TestdataHerdEntity; import ai.timefold.solver.core.testdomain.multientity.TestdataLeadEntity; import ai.timefold.solver.core.testdomain.multientity.TestdataMultiEntitySolution; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarSolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java index 8f8dfd1af21..30355be02db 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java @@ -14,8 +14,8 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataOtherValue; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataOtherValue; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactoryTest.java index e66547d0460..fb44e867a34 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactoryTest.java @@ -53,7 +53,7 @@ void unfoldingFailsIfThereIsNoListVariable() { assertThatIllegalArgumentException() .isThrownBy(() -> moveSelectorFactory.buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false)) - .withMessageContaining("cannot unfold"); + .withMessageContaining("it cannot be deduced automatically"); } @Test @@ -70,7 +70,7 @@ void explicitConfigMustUseListVariable() { assertThatIllegalArgumentException() .isThrownBy(() -> moveSelectorFactory.buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false)) - .withMessageContaining("not a planning list variable"); + .withMessageContaining("it cannot be deduced automatically"); } // TODO test or remove secondary value selector config diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactoryTest.java index c40c203b05f..7f034b035f5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactoryTest.java @@ -14,11 +14,11 @@ import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListChangeMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; -import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.list.RandomSubListSelector; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,17 +29,30 @@ class SubListChangeMoveSelectorFactoryTest { @Test void buildMoveSelector() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig(); - SubListChangeMoveSelectorFactory moveSelectorFactory = - new SubListChangeMoveSelectorFactory<>(config); + var config = new SubListChangeMoveSelectorConfig(); + var moveSelectorFactory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListChangeMoveSelector selector = - (RandomSubListChangeMoveSelector) moveSelectorFactory - .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, - false); + var selector = (RandomSubListChangeMoveSelector) moveSelectorFactory + .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, + false); + + assertThat(selector.isCountable()).isTrue(); + assertThat(selector.isNeverEnding()).isTrue(); + assertThat(selector.isSelectReversingMoveToo()).isTrue(); + } + + @Test + void buildMoveSelectorMultiEntity() { + var config = new SubListChangeMoveSelectorConfig(); + var moveSelectorFactory = new SubListChangeMoveSelectorFactory(config); + + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataMixedMultiEntitySolution.buildSolutionDescriptor()); + + var selector = (RandomSubListChangeMoveSelector) moveSelectorFactory + .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, + false); assertThat(selector.isCountable()).isTrue(); assertThat(selector.isNeverEnding()).isTrue(); @@ -48,49 +61,44 @@ void buildMoveSelector() { @Test void disableSelectReversingMoveToo() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig(); + var config = new SubListChangeMoveSelectorConfig(); config.setSelectReversingMoveToo(false); - SubListChangeMoveSelectorFactory moveSelectorFactory = - new SubListChangeMoveSelectorFactory<>(config); + var moveSelectorFactory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListChangeMoveSelector selector = - (RandomSubListChangeMoveSelector) moveSelectorFactory - .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, - false); + var selector = (RandomSubListChangeMoveSelector) moveSelectorFactory + .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, + false); assertThat(selector.isSelectReversingMoveToo()).isFalse(); } @Test void unfoldingFailsIfThereIsNoListVariable() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig(); - SubListChangeMoveSelectorFactory moveSelectorFactory = new SubListChangeMoveSelectorFactory<>(config); + var config = new SubListChangeMoveSelectorConfig(); + var moveSelectorFactory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataSolution.buildSolutionDescriptor()); assertThatIllegalArgumentException() .isThrownBy(() -> moveSelectorFactory.buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, false)) - .withMessageContaining("cannot unfold"); + .withMessageContaining("it cannot be deduced automatically"); } @Test void explicitConfigMustUseListVariable() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig() + var config = new SubListChangeMoveSelectorConfig() .withSubListSelectorConfig(new SubListSelectorConfig() .withValueSelectorConfig(new ValueSelectorConfig("value"))) .withDestinationSelectorConfig(new DestinationSelectorConfig() .withEntitySelectorConfig(new EntitySelectorConfig(TestdataEntity.class)) .withValueSelectorConfig(new ValueSelectorConfig("value"))); - SubListChangeMoveSelectorFactory moveSelectorFactory = - new SubListChangeMoveSelectorFactory<>(config); + var moveSelectorFactory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataSolution.buildSolutionDescriptor()); assertThatIllegalArgumentException() @@ -100,14 +108,14 @@ void explicitConfigMustUseListVariable() { } static SubListChangeMoveSelectorConfig minimumSize_SubListSelector() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig() + var config = new SubListChangeMoveSelectorConfig() .withSubListSelectorConfig(new SubListSelectorConfig().withMinimumSubListSize(10)); config.setMinimumSubListSize(10); return config; } static SubListChangeMoveSelectorConfig maximumSize_SubListSelector() { - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig() + var config = new SubListChangeMoveSelectorConfig() .withSubListSelectorConfig(new SubListSelectorConfig().withMaximumSubListSize(10)); config.setMaximumSubListSize(10); return config; @@ -123,10 +131,9 @@ static Stream wrongConfigurations() { @MethodSource("wrongConfigurations") void failFast_ifSubListSizeOnBothMoveSelectorAndSubListSelector( SubListChangeMoveSelectorConfig config, String propertyName, String childConfigName) { - SubListChangeMoveSelectorFactory factory = new SubListChangeMoveSelectorFactory<>(config); + var factory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); assertThatIllegalArgumentException() .isThrownBy(() -> factory.buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, @@ -136,21 +143,19 @@ void failFast_ifSubListSizeOnBothMoveSelectorAndSubListSelector( @Test void transferDeprecatedSubListSizeToChildSelector() { - int minimumSubListSize = 21; - int maximumSubListSize = 445; - SubListChangeMoveSelectorConfig config = new SubListChangeMoveSelectorConfig(); + var minimumSubListSize = 21; + var maximumSubListSize = 445; + var config = new SubListChangeMoveSelectorConfig(); config.setMinimumSubListSize(minimumSubListSize); config.setMaximumSubListSize(maximumSubListSize); - SubListChangeMoveSelectorFactory factory = new SubListChangeMoveSelectorFactory<>(config); + var factory = new SubListChangeMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListChangeMoveSelector moveSelector = - (RandomSubListChangeMoveSelector) factory - .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, - false); + var moveSelector = (RandomSubListChangeMoveSelector) factory + .buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM, + false); assertThat(((RandomSubListSelector) moveSelector.getSubListSelector()).getMinimumSubListSize()) .isEqualTo(minimumSubListSize); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactoryTest.java index 86a49dd09ef..12422e928d1 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListSwapMoveSelectorFactoryTest.java @@ -8,11 +8,13 @@ import java.util.stream.Stream; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; +import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListSwapMoveSelectorConfig; -import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.list.RandomSubListSelector; +import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -23,14 +25,13 @@ class SubListSwapMoveSelectorFactoryTest { @Test void buildBaseMoveSelector() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig(); - SubListSwapMoveSelectorFactory factory = - new SubListSwapMoveSelectorFactory<>(config); + var config = new SubListSwapMoveSelectorConfig(); + var factory = new SubListSwapMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListSwapMoveSelector selector = + var selector = (RandomSubListSwapMoveSelector) factory.buildBaseMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, true); @@ -39,17 +40,45 @@ void buildBaseMoveSelector() { assertThat(selector.isSelectReversingMoveToo()).isTrue(); } + @Test + void buildMoveSelectorMultiEntity() { + var config = new SubListSwapMoveSelectorConfig(); + var factory = new SubListSwapMoveSelectorFactory(config); + + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataMixedMultiEntitySolution.buildSolutionDescriptor()); + + var selector = + (RandomSubListSwapMoveSelector) factory.buildBaseMoveSelector( + heuristicConfigPolicy, + SelectionCacheType.JUST_IN_TIME, true); + + assertThat(selector.isCountable()).isTrue(); + assertThat(selector.isNeverEnding()).isTrue(); + assertThat(selector.isSelectReversingMoveToo()).isTrue(); + } + + @Test + void unfoldingFailsIfThereIsNoListVariable() { + var config = new SubListSwapMoveSelectorConfig(); + var moveSelectorFactory = new SubListSwapMoveSelectorFactory(config); + + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataSolution.buildSolutionDescriptor()); + + assertThatIllegalArgumentException() + .isThrownBy(() -> moveSelectorFactory.buildMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, + SelectionOrder.RANDOM, false)) + .withMessageContaining("it cannot be deduced automatically"); + } + @Test void disableSelectReversingMoveToo() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig(); + var config = new SubListSwapMoveSelectorConfig(); config.setSelectReversingMoveToo(false); - SubListSwapMoveSelectorFactory factory = - new SubListSwapMoveSelectorFactory<>(config); + var factory = new SubListSwapMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListSwapMoveSelector selector = + var selector = (RandomSubListSwapMoveSelector) factory.buildBaseMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, true); @@ -57,28 +86,28 @@ void disableSelectReversingMoveToo() { } static SubListSwapMoveSelectorConfig minimumSize_SubListSelector() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig() + var config = new SubListSwapMoveSelectorConfig() .withSubListSelectorConfig(new SubListSelectorConfig().withMinimumSubListSize(10)); config.setMinimumSubListSize(10); return config; } static SubListSwapMoveSelectorConfig maximumSize_SubListSelector() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig() + var config = new SubListSwapMoveSelectorConfig() .withSubListSelectorConfig(new SubListSelectorConfig().withMaximumSubListSize(10)); config.setMaximumSubListSize(10); return config; } static SubListSwapMoveSelectorConfig minimumSize_SecondarySubListSelector() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig() + var config = new SubListSwapMoveSelectorConfig() .withSecondarySubListSelectorConfig(new SubListSelectorConfig().withMinimumSubListSize(10)); config.setMinimumSubListSize(10); return config; } static SubListSwapMoveSelectorConfig maximumSize_SecondarySubListSelector() { - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig() + var config = new SubListSwapMoveSelectorConfig() .withSecondarySubListSelectorConfig(new SubListSelectorConfig().withMaximumSubListSize(10)); config.setMaximumSubListSize(10); return config; @@ -96,10 +125,9 @@ static Stream wrongConfigurations() { @MethodSource("wrongConfigurations") void failFast_ifSubListSizeOnBothMoveSelectorAndSubListSelector( SubListSwapMoveSelectorConfig config, String propertyName, String childConfigName) { - SubListSwapMoveSelectorFactory factory = new SubListSwapMoveSelectorFactory<>(config); + var factory = new SubListSwapMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); assertThatIllegalArgumentException() .isThrownBy(() -> factory.buildBaseMoveSelector(heuristicConfigPolicy, SelectionCacheType.JUST_IN_TIME, true)) @@ -108,20 +136,18 @@ void failFast_ifSubListSizeOnBothMoveSelectorAndSubListSelector( @Test void transferDeprecatedSubListSizeToChildSelectors() { - int minimumSubListSize = 21; - int maximumSubListSize = 445; - SubListSwapMoveSelectorConfig config = new SubListSwapMoveSelectorConfig(); + var minimumSubListSize = 21; + var maximumSubListSize = 445; + var config = new SubListSwapMoveSelectorConfig(); config.setMinimumSubListSize(minimumSubListSize); config.setMaximumSubListSize(maximumSubListSize); - SubListSwapMoveSelectorFactory factory = new SubListSwapMoveSelectorFactory<>(config); + var factory = new SubListSwapMoveSelectorFactory(config); - HeuristicConfigPolicy heuristicConfigPolicy = - buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); + var heuristicConfigPolicy = buildHeuristicConfigPolicy(TestdataListSolution.buildSolutionDescriptor()); - RandomSubListSwapMoveSelector moveSelector = - (RandomSubListSwapMoveSelector) factory.buildBaseMoveSelector(heuristicConfigPolicy, - SelectionCacheType.JUST_IN_TIME, true); + var moveSelector = (RandomSubListSwapMoveSelector) factory.buildBaseMoveSelector(heuristicConfigPolicy, + SelectionCacheType.JUST_IN_TIME, true); assertThat(((RandomSubListSelector) moveSelector.getLeftSubListSelector()).getMinimumSubListSize()) .isEqualTo(minimumSubListSize); assertThat(((RandomSubListSelector) moveSelector.getLeftSubListSelector()).getMaximumSubListSize()) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ReinitializeVariableValueSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ReinitializeVariableValueSelectorTest.java index 01a3c36e8d0..c24b1d3ca82 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ReinitializeVariableValueSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ReinitializeVariableValueSelectorTest.java @@ -17,7 +17,7 @@ import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.multivar.basic.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index dc9c509434e..a199116cc21 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -1,5 +1,8 @@ package ai.timefold.solver.core.impl.solver; +import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY; +import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE; +import static ai.timefold.solver.core.config.solver.PreviewFeature.DECLARATIVE_SHADOW_VARIABLES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.fail; @@ -38,11 +41,33 @@ import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig; +import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig; +import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; +import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner; +import ai.timefold.solver.core.config.heuristic.selector.entity.pillar.PillarSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.factory.MoveIteratorFactoryConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarSwapMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.RuinRecreateMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.SubChainChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.SubChainSwapMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListRuinRecreateMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListChangeMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListSwapMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner; +import ai.timefold.solver.core.config.heuristic.selector.value.chained.SubChainSelectorConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchType; import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig; @@ -63,6 +88,7 @@ import ai.timefold.solver.core.impl.score.DummySimpleScoreEasyScoreCalculator; import ai.timefold.solver.core.impl.score.constraint.DefaultConstraintMatchTotal; import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; +import ai.timefold.solver.core.impl.util.Pair; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.testdomain.TestdataEntity; @@ -78,6 +104,7 @@ import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; +import ai.timefold.solver.core.testdomain.list.TestdataListVarEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.pinned.TestdataPinnedListEntity; import ai.timefold.solver.core.testdomain.list.pinned.TestdataPinnedListSolution; import ai.timefold.solver.core.testdomain.list.pinned.TestdataPinnedListValue; @@ -88,9 +115,25 @@ import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListEntity; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListSolution; import ai.timefold.solver.core.testdomain.list.unassignedvar.TestdataAllowsUnassignedValuesListValue; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntityFirstEntity; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySecondEntity; +import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution; +import ai.timefold.solver.core.testdomain.mixed.singleentity.MixedCustomMoveIteratorFactory; +import ai.timefold.solver.core.testdomain.mixed.singleentity.MixedCustomPhaseCommand; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedEntity; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedOtherValue; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedSolution; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedValue; +import ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar.TestdataUnassignedMixedEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar.TestdataUnassignedMixedEntity; +import ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar.TestdataUnassignedMixedSolution; import ai.timefold.solver.core.testdomain.multientity.TestdataHerdEntity; import ai.timefold.solver.core.testdomain.multientity.TestdataLeadEntity; import ai.timefold.solver.core.testdomain.multientity.TestdataMultiEntitySolution; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; +import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarSolution; import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedEntity; import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.score.TestdataHardSoftScoreSolution; @@ -106,6 +149,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Metrics; @@ -1169,23 +1214,6 @@ void solveWithCHAllowsUnassignedValuesListVariableAndTerminateInStep() { assertThat(bestSolution.getScore()).isEqualTo(SimpleScore.of(1)); } - @Test - void solveWithMultipleGenuinePlanningEntities() { - var solverConfig = new SolverConfig() - .withSolutionClass(TestdataMultiEntitySolution.class) - .withEntityClasses(TestdataLeadEntity.class, TestdataHerdEntity.class) - .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class) - .withTerminationConfig(new TerminationConfig().withBestScoreLimit("0")); - - var solution = new TestdataMultiEntitySolution("s1"); - solution.setValueList(Arrays.asList(new TestdataValue("v1"), new TestdataValue("v2"))); - solution.setLeadEntityList(Arrays.asList(new TestdataLeadEntity("lead1"), new TestdataLeadEntity("lead2"))); - solution.setHerdEntityList(Arrays.asList(new TestdataHerdEntity("herd1"), new TestdataHerdEntity("herd2"))); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - } - /** * Verifies PLANNER-2798. */ @@ -1198,9 +1226,9 @@ void solveWithMultipleChainedPlanningEntities() { .withTerminationConfig(new TerminationConfig().withBestScoreLimit("0")) .withPhases( // Each planning entity class needs a separate CH phase. - new ConstructionHeuristicPhaseConfig().withEntityPlacerConfigList(new QueuedEntityPlacerConfig() + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig() .withEntitySelectorConfig(new EntitySelectorConfig(TestdataChainedBrownEntity.class))), - new ConstructionHeuristicPhaseConfig().withEntityPlacerConfigList(new QueuedEntityPlacerConfig() + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig() .withEntitySelectorConfig(new EntitySelectorConfig(TestdataChainedGreenEntity.class))), new LocalSearchPhaseConfig().withMoveSelectorConfig(new UnionMoveSelectorConfig().withMoveSelectors( new ChangeMoveSelectorConfig(), @@ -1379,6 +1407,673 @@ void solveWithPlanningListVariablePinIndexUnfair() { .isEmpty(); } + @Test + void solveCustomConfigListVariable() { + var valueSelectorConfig = new ValueSelectorConfig("valueList") + .withId("valueList"); + var mimicReplayingValueSelectorConfig = new ValueSelectorConfig() + .withMimicSelectorRef("valueList") + .withVariableName("valueList"); + var valuePlacerConfig = new QueuedValuePlacerConfig() + .withValueSelectorConfig(valueSelectorConfig) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig(mimicReplayingValueSelectorConfig)); + + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class) + .withPhases(new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(valuePlacerConfig), + new LocalSearchPhaseConfig().withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) + .withEasyScoreCalculatorClass(TestdataListVarEasyScoreCalculator.class); + + var problem = TestdataListSolution.generateUninitializedSolution(2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getValueList().isEmpty())) + .isEmpty(); + } + + @ParameterizedTest + @MethodSource("generateMovesForSingleVar") + void solveSingleVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataSolution.class, TestdataEntity.class) + .withPhases(new ConstructionHeuristicPhaseConfig(), localSearchConfig) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = TestdataSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .doesNotThrowAnyException(); + } + + private static List generateMovesForSingleVar() { + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Pillar change - basic + allMoveSelectionConfigList.add(new PillarChangeMoveSelectorConfig()); + // Pilar swap - basic + allMoveSelectionConfigList.add(new PillarSwapMoveSelectorConfig()); + // R&R - basic + allMoveSelectionConfigList.add(new RuinRecreateMoveSelectorConfig()); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @ParameterizedTest + @MethodSource("generateMovesForChainedVar") + void solveChainedVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataChainedSolution.class, TestdataChainedEntity.class) + .withPhases(new ConstructionHeuristicPhaseConfig(), localSearchConfig) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = TestdataChainedSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .doesNotThrowAnyException(); + } + + private static List generateMovesForChainedVar() { + var allMoveSelectionConfigList = new ArrayList(); + // Change - chained + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - chained + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Tail Chain - chained + allMoveSelectionConfigList.add(new TailChainSwapMoveSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("chainedObject"))); + // Subchain chain - chained + allMoveSelectionConfigList + .add(new SubChainChangeMoveSelectorConfig().withSubChainSelectorConfig(new SubChainSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("chainedObject"))) + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("chainedObject"))); + // Subchain swap - chained + allMoveSelectionConfigList + .add(new SubChainSwapMoveSelectorConfig().withSubChainSelectorConfig(new SubChainSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("chainedObject")))); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @ParameterizedTest + @MethodSource("generateMovesForListVar") + void solveListVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig(), localSearchConfig) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = TestdataListSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .doesNotThrowAnyException(); + } + + private static List generateMovesForListVar() { + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ListChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new ListSwapMoveSelectorConfig()); + // Pillar change - basic + allMoveSelectionConfigList.add(new SubListChangeMoveSelectorConfig()); + // Pilar swap - basic + allMoveSelectionConfigList.add(new SubListSwapMoveSelectorConfig()); + // R&R - basic + allMoveSelectionConfigList.add(new ListRuinRecreateMoveSelectorConfig()); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @ParameterizedTest + @MethodSource("generateMovesForMultiVar") + void solveMultiVarMoveConfig(MoveSelectorConfig moveSelectionConfig) { + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMultiVarSolution.class, TestdataMultiVarEntity.class) + .withPhases(new ConstructionHeuristicPhaseConfig(), localSearchConfig) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class); + + var problem = TestdataMultiVarSolution.generateUninitializedSolution(2, 2); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)) + .doesNotThrowAnyException(); + } + + private static List generateMovesForMultiVar() { + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Pillar change - basic + var pillarChangeMoveSelectorConfig = new PillarChangeMoveSelectorConfig(); + var pillarChangeEntitySelectorConfig = + new EntitySelectorConfig().withEntityClass(TestdataMultiVarEntity.class); + var pillarChangeValueSelectorConfig = new ValueSelectorConfig().withVariableName("primaryValue"); + pillarChangeMoveSelectorConfig + .withPillarSelectorConfig(new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig)) + .withValueSelectorConfig(pillarChangeValueSelectorConfig); + allMoveSelectionConfigList.add(pillarChangeMoveSelectorConfig); + // Pilar swap - basic + allMoveSelectionConfigList.add(new PillarSwapMoveSelectorConfig().withPillarSelectorConfig( + new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig))); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @Test + void solveMultiEntity() { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMultiEntitySolution.class, TestdataLeadEntity.class, TestdataHerdEntity.class) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class) + .withPhaseList(Collections.emptyList()) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + + var problem = TestdataMultiEntitySolution.generateUninitializedSolution(3, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution).isNotNull(); + } + + @ParameterizedTest + @MethodSource("generateMovesForMultiEntity") + void solveMultiEntityMoveConfig(MoveSelectorConfig moveSelectionConfig) { + // Construction Heuristic + var leadConstructionHeuristicConfig = new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId(TestdataLeadEntity.class.getName()) + .withEntityClass(TestdataLeadEntity.class))); + var herdConstructionHeuristicConfig = new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId(TestdataHerdEntity.class.getName()) + .withEntityClass(TestdataHerdEntity.class))); + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMultiEntitySolution.class, TestdataLeadEntity.class, TestdataHerdEntity.class) + .withPhases(leadConstructionHeuristicConfig, herdConstructionHeuristicConfig, localSearchConfig) + .withEasyScoreCalculatorClass(DummySimpleScoreEasyScoreCalculator.class) + .withTerminationConfig(new TerminationConfig().withBestScoreLimit("0")); + + var problem = TestdataMultiEntitySolution.generateUninitializedSolution(2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution).isNotNull(); + } + + private static List generateMovesForMultiEntity() { + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Pillar change - basic + var pillarChangeMoveSelectorConfig = new PillarChangeMoveSelectorConfig(); + var pillarChangeEntitySelectorConfig = + new EntitySelectorConfig().withEntityClass(TestdataLeadEntity.class); + var pillarChangeValueSelectorConfig = new ValueSelectorConfig().withVariableName("value"); + pillarChangeMoveSelectorConfig + .withPillarSelectorConfig(new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig)) + .withValueSelectorConfig(pillarChangeValueSelectorConfig); + allMoveSelectionConfigList.add(pillarChangeMoveSelectorConfig); + // Pilar swap - basic + allMoveSelectionConfigList.add(new PillarSwapMoveSelectorConfig().withPillarSelectorConfig( + new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig))); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @Test + void solveMixedModel() { + // Same size for both list and basic variables + executeSolveMixedModel(2, 2, 2); + // Bigger list planning values size + executeSolveMixedModel(2, 3, 2); + executeSolveMixedModel(2, 2, 3); + // Bigger basic planning values size + executeSolveMixedModel(3, 2, 2); + } + + @Test + void solveMultiEntityMixedModel() { + // Same size for both list and basic variables + executeSolveMultiEntityMixedModel(2, 2, 2); + // Bigger list planning values size + executeSolveMultiEntityMixedModel(2, 3, 2); + executeSolveMultiEntityMixedModel(2, 2, 3); + // Bigger basic planning values size + executeSolveMultiEntityMixedModel(3, 2, 2); + } + + private void executeSolveMixedModel(int entitySize, int valueSize, int otherValueSize) { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhaseList(Collections.emptyList()) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16)) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(entitySize, valueSize, otherValueSize); + var solution = PlannerTestUtils.solve(solverConfig, problem); + + // Check the solution + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null)) + .isEmpty(); + + var expectedSize = Math.max(entitySize - valueSize, 0L); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getValueList().isEmpty()) + .count()).isEqualTo(expectedSize); + + // Check custom listener execution + assertThat(solution.getValueList().stream().allMatch(v -> v.getShadowVariableListenerValue().equals(v.getIndex()))) + .isTrue(); + + // Check cascading shadow variable + assertThat(solution.getValueList().stream().allMatch(v -> v.getCascadingShadowVariableValue().equals(v.getIndex() + 1))) + .isTrue(); + + // Check declarative shadow variable from basic variable - genuine entity + assertThat(solution.getEntityList().stream() + .allMatch(v -> v.getDeclarativeShadowVariableValue().equals(v.getBasicValue().getStrength()))) + .isTrue(); + + // Check declarative shadow variable from basic variable - shadow entity + assertThat(solution.getOtherValueList().stream() + .allMatch(v -> v.getDeclarativeShadowVariableValue().equals(v.getEntityList().size() + 2))) + .isTrue(); + + // Check declarative shadow variable from list variable - shadow entity + assertThat( + solution.getValueList().stream().allMatch(v -> v.getDeclarativeShadowVariableValue().equals(v.getIndex() + 2))) + .isTrue(); + } + + void executeSolveMultiEntityMixedModel(int entitySize, int valueSize, int otherValueSize) { + // Solver Config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedMultiEntitySolution.class, TestdataMixedMultiEntityFirstEntity.class, + TestdataMixedMultiEntitySecondEntity.class) + .withPhaseList(Collections.emptyList()) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)) + .withEasyScoreCalculatorClass(TestdataMixedEntityEasyScoreCalculator.class); + + var problem = TestdataMixedMultiEntitySolution.generateUninitializedSolution(entitySize, valueSize, otherValueSize); + var solution = PlannerTestUtils.solve(solverConfig, problem); + var expectedSize = Math.max(entitySize - valueSize, 0L); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getValueList().isEmpty()) + .count()).isEqualTo(expectedSize); + assertThat(solution.getOtherEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null)) + .isEmpty(); + for (var entity : solution.getOtherEntityList()) { + // The strength comparator will cause the basic variables to differ during the CH phase, + // and LS will find no improvement + assertThat(entity.getBasicValue()).isNotSameAs(entity.getSecondBasicValue()); + } + } + + @Test + void solveMixedModelCustomMove() { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()), + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList"))), + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(new MoveIteratorFactoryConfig() + .withMoveIteratorFactoryClass(MixedCustomMoveIteratorFactory.class)) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(2, 2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + + // Check the solution + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) + .isEmpty(); + } + + @Test + void solveMixedModelCustomPhase() { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()), + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList"))), + new CustomPhaseConfig() + .withCustomPhaseCommands(new MixedCustomPhaseCommand()) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(2, 2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + + // Check the solution + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) + .isEmpty(); + } + + private static List> getSortMannerList() { + var sortMannerList = new ArrayList>(); + for (var valueSortManner : ValueSorterManner.values()) { + sortMannerList.add(new Pair<>(DECREASING_DIFFICULTY, valueSortManner)); + sortMannerList.add(new Pair<>(DECREASING_DIFFICULTY_IF_AVAILABLE, valueSortManner)); + } + return sortMannerList; + } + + @ParameterizedTest + @MethodSource("getSortMannerList") + void solveMixedModelWithSortManner(Pair sorterManner) { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()), + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList"))), + new LocalSearchPhaseConfig() + .withMoveSelectorConfig( + new ChangeMoveSelectorConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterManner(sorterManner.key())) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withVariableName("basicValue") + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterManner(sorterManner.value()))) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16))) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(2, 2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) + .isEmpty(); + } + + @Test + void solvePinnedMixedModel() { + // We don't enable the LS because we want to ensure the pinned entity remains uninitialized + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()), + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList")))) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(3, 2, 2); + // Pin the first entity + problem.getEntityList().get(0).setPinned(true); + problem.getEntityList().get(0).setPinnedIndex(0); + var solution = PlannerTestUtils.solve(solverConfig, problem); + // The first entity should remain unchanged + assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); + assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNull(); + assertThat(solution.getEntityList().get(0).getValueList()).isEmpty(); + } + + @Test + void solveUnassignedMixedModel() { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataUnassignedMixedSolution.class, TestdataUnassignedMixedEntity.class) + .withPhaseList(Collections.emptyList()) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16)) + .withEasyScoreCalculatorClass(TestdataUnassignedMixedEasyScoreCalculator.class); + + var problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 2, 2); + // Block values and make the basic and list variables unassigned + problem.getValueList().get(0).setBlocked(true); + problem.getValueList().get(1).setBlocked(true); + problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().get(1).setBlocked(true); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null)) + .hasSize(2); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getSecondBasicValue() != null)) + .hasSize(2); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getValueList().isEmpty())) + .hasSize(2); + } + + @Test + void solvePinnedAndUnassignedMixedModel() { + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataUnassignedMixedSolution.class, TestdataUnassignedMixedEntity.class) + .withPhaseList(Collections.emptyList()) + .withTerminationConfig(new TerminationConfig().withStepCountLimit(16)) + .withEasyScoreCalculatorClass(TestdataUnassignedMixedEasyScoreCalculator.class); + + // Pin the entire first entity + var problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 2, 2); + problem.getEntityList().get(0).setPinned(true); + problem.getEntityList().get(0).setBasicValue(problem.getOtherValueList().get(0)); + problem.getEntityList().get(0).setSecondBasicValue(problem.getOtherValueList().get(0)); + problem.getEntityList().get(0).setValueList(List.of(problem.getValueList().get(0))); + // Block values and make the basic and list variables unassigned + problem.getValueList().get(0).setBlocked(true); + problem.getValueList().get(1).setBlocked(true); + problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().get(1).setBlocked(true); + var solution = PlannerTestUtils.solve(solverConfig, problem); + // The first entity should remain unchanged + assertThat(solution.getEntityList().get(0).getBasicValue()).isNotNull(); + assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); + assertThat(solution.getEntityList().get(0).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); + assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); + assertThat(solution.getEntityList().get(1).getValueList()).isEmpty(); + + // Pin partially the first entity list + problem = TestdataUnassignedMixedSolution.generateUninitializedSolution(2, 4, 2); + problem.getEntityList().get(0).setPinnedIndex(2); + problem.getEntityList().get(0).setValueList(problem.getValueList().subList(1, 3)); + // Block values and make the basic variable unassigned + problem.getOtherValueList().get(0).setBlocked(true); + problem.getOtherValueList().get(1).setBlocked(true); + solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution.getEntityList().get(0).getBasicValue()).isNull(); + assertThat(solution.getEntityList().get(0).getSecondBasicValue()).isNotNull(); + // The pinning index fixed the values 1 and 2. The only remaining option is values are 0 and 3. + // The score is bigger when the list size is 3 + assertThat(solution.getEntityList().get(0).getValueList()).hasSize(3); + assertThat(solution.getEntityList().get(0).getValueList()) + .hasSameElementsAs( + List.of(problem.getValueList().get(1), problem.getValueList().get(2), problem.getValueList().get(0))); + assertThat(solution.getEntityList().get(1).getBasicValue()).isNull(); + assertThat(solution.getEntityList().get(1).getSecondBasicValue()).isNotNull(); + assertThat(solution.getEntityList().get(1).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(1).getValueList()).hasSameElementsAs(List.of(problem.getValueList().get(3))); + } + + private static List generateMovesForMixedModel() { + // Local Search + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Pillar change - basic + var pillarChangeMoveSelectorConfig = new PillarChangeMoveSelectorConfig(); + var pillarChangeEntitySelectorConfig = + new EntitySelectorConfig().withEntityClass(TestdataMixedEntity.class); + var pillarChangeValueSelectorConfig = new ValueSelectorConfig().withVariableName("basicValue"); + pillarChangeMoveSelectorConfig + .withPillarSelectorConfig(new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig)) + .withValueSelectorConfig(pillarChangeValueSelectorConfig); + allMoveSelectionConfigList.add(pillarChangeMoveSelectorConfig); + // Pilar swap - basic + allMoveSelectionConfigList.add(new PillarSwapMoveSelectorConfig().withPillarSelectorConfig( + new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig))); + // Change - list + allMoveSelectionConfigList.add(new ListChangeMoveSelectorConfig()); + // Swap - list + allMoveSelectionConfigList.add(new ListSwapMoveSelectorConfig()); + // Sublist change - list + allMoveSelectionConfigList.add(new SubListChangeMoveSelectorConfig()); + // Sublist swap - list + allMoveSelectionConfigList.add(new SubListSwapMoveSelectorConfig().withSubListSelectorConfig( + new SubListSelectorConfig().withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList")))); + // KOpt - list + allMoveSelectionConfigList.add(new KOptListMoveSelectorConfig()); + // R&R - list + allMoveSelectionConfigList.add(new ListRuinRecreateMoveSelectorConfig()); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @ParameterizedTest + @MethodSource("generateMovesForMixedModel") + void solveMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedSolution.class, TestdataMixedEntity.class, TestdataMixedValue.class, + TestdataMixedOtherValue.class) + .withPreviewFeature(DECLARATIVE_SHADOW_VARIABLES) + .withPhases(new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()), + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList"))), + localSearchConfig) + .withEasyScoreCalculatorClass(TestdataMixedEasyScoreCalculator.class); + + var problem = TestdataMixedSolution.generateUninitializedSolution(2, 2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + assertThat(solution.getEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null || e.getValueList().isEmpty())) + .isEmpty(); + } + + private static List generateMovesForMultiEntityMixedModel() { + // Local Search + var allMoveSelectionConfigList = new ArrayList(); + // Change - basic + allMoveSelectionConfigList.add(new ChangeMoveSelectorConfig()); + // Swap - basic + allMoveSelectionConfigList.add(new SwapMoveSelectorConfig()); + // Pillar change - basic + var pillarChangeMoveSelectorConfig = new PillarChangeMoveSelectorConfig(); + var pillarChangeEntitySelectorConfig = + new EntitySelectorConfig().withEntityClass(TestdataMixedMultiEntitySecondEntity.class); + var pillarChangeValueSelectorConfig = new ValueSelectorConfig().withVariableName("basicValue"); + pillarChangeMoveSelectorConfig + .withPillarSelectorConfig(new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig)) + .withValueSelectorConfig(pillarChangeValueSelectorConfig); + allMoveSelectionConfigList.add(pillarChangeMoveSelectorConfig); + // Pilar swap - basic + allMoveSelectionConfigList.add(new PillarSwapMoveSelectorConfig().withPillarSelectorConfig( + new PillarSelectorConfig().withEntitySelectorConfig(pillarChangeEntitySelectorConfig))); + // Change - list + allMoveSelectionConfigList.add(new ListChangeMoveSelectorConfig()); + // Swap - list + allMoveSelectionConfigList.add(new ListSwapMoveSelectorConfig()); + // Sublist change - list + allMoveSelectionConfigList.add(new SubListChangeMoveSelectorConfig()); + // Sublist swap - list + allMoveSelectionConfigList.add(new SubListSwapMoveSelectorConfig()); + // KOpt - list + allMoveSelectionConfigList.add(new KOptListMoveSelectorConfig()); + // R&R - list + allMoveSelectionConfigList.add(new ListRuinRecreateMoveSelectorConfig()); + // Union of all moves + allMoveSelectionConfigList.add(new UnionMoveSelectorConfig(List.copyOf(allMoveSelectionConfigList))); + return allMoveSelectionConfigList; + } + + @ParameterizedTest + @MethodSource("generateMovesForMultiEntityMixedModel") + void solveMultiEntityMoveConfigMixedModel(MoveSelectorConfig moveSelectionConfig) { + // Construction Heuristic + var constructionHeuristicValuePlacer = + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withEntityClass(TestdataMixedMultiEntityFirstEntity.class) + .withValueSelectorConfig(new ValueSelectorConfig().withVariableName("valueList"))); + var constructionHeuristicEntityPlacer = + new ConstructionHeuristicPhaseConfig().withEntityPlacerConfig(new QueuedEntityPlacerConfig()); + // Local search + var localSearchConfig = + new LocalSearchPhaseConfig() + .withMoveSelectorConfig(moveSelectionConfig) + .withTerminationConfig(new TerminationConfig().withMoveCountLimit(40L)); + // Solver Config + var solverConfig = PlannerTestUtils.buildSolverConfig( + TestdataMixedMultiEntitySolution.class, TestdataMixedMultiEntityFirstEntity.class, + TestdataMixedMultiEntitySecondEntity.class) + .withPhases(constructionHeuristicValuePlacer, constructionHeuristicEntityPlacer, localSearchConfig) + .withEasyScoreCalculatorClass(TestdataMixedEntityEasyScoreCalculator.class); + + var problem = TestdataMixedMultiEntitySolution.generateUninitializedSolution(3, 2, 2); + var solution = PlannerTestUtils.solve(solverConfig, problem); + // Three planning entities and two planning values. One entity will remain unscheduled. + assertThat(solution.getEntityList().stream() + .filter(e -> e.getValueList().isEmpty()) + .count()).isEqualTo(1); + assertThat(solution.getOtherEntityList().stream() + .filter(e -> e.getBasicValue() == null || e.getSecondBasicValue() == null)) + .isEmpty(); + for (var entity : solution.getOtherEntityList()) { + // The strength comparator will cause the basic variables to differ during the CH phase, + // and LS will find no improvement + assertThat(entity.getBasicValue()).isNotSameAs(entity.getSecondBasicValue()); + } + } + public static final class MinimizeUnusedEntitiesEasyScoreCalculator implements EasyScoreCalculator { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarEntity.java index d918408745e..90cb5c4fa4d 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarEntity.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType; import ai.timefold.solver.core.testdomain.TestdataObject; import ai.timefold.solver.core.testdomain.chained.TestdataChainedObject; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarValue; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedValue; @PlanningEntity public class TestdataInvalidMultiVarEntity extends TestdataObject implements TestdataChainedObject { @@ -18,7 +18,7 @@ public class TestdataInvalidMultiVarEntity extends TestdataObject implements Tes private TestdataChainedObject chainedValue; @PlanningListVariable(valueRangeProviderRefs = "valueRange") - private List valueList; + private List valueList; public TestdataInvalidMultiVarEntity(String code) { super(code); @@ -32,11 +32,11 @@ public void setChainedValue(TestdataChainedObject chainedValue) { this.chainedValue = chainedValue; } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarSolution.java index 4f303754154..13a72ee4af6 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/invalid/multivar/TestdataInvalidMultiVarSolution.java @@ -9,7 +9,7 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.testdomain.chained.TestdataChainedAnchor; -import ai.timefold.solver.core.testdomain.multivar.list.singleentity.TestdataListMultiVarValue; +import ai.timefold.solver.core.testdomain.mixed.singleentity.TestdataMixedValue; @PlanningSolution public class TestdataInvalidMultiVarSolution { @@ -21,7 +21,7 @@ public static SolutionDescriptor buildSolutionD @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty - private List valueList; + private List valueList; @ValueRangeProvider(id = "chainedAnchorRange") @PlanningEntityCollectionProperty private List chainedAnchorList; @@ -31,11 +31,11 @@ public static SolutionDescriptor buildSolutionD @PlanningScore private SimpleScore score; - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesEntity.java deleted file mode 100644 index df52ae5b3a3..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesEntity.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.mixed; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.TestdataValue; - -@PlanningEntity -public class TestdataMixedVariablesEntity extends TestdataObject { - - public static EntityDescriptor buildEntityDescriptor() { - return TestdataMixedVariablesSolution.buildSolutionDescriptor() - .findEntityDescriptorOrFail(TestdataMixedVariablesEntity.class); - } - - public static ListVariableDescriptor buildVariableDescriptorForValueList() { - return (ListVariableDescriptor) buildEntityDescriptor() - .getGenuineVariableDescriptor("valueList"); - } - - @PlanningListVariable(valueRangeProviderRefs = "valueRange") - private final List valueList; - @PlanningVariable(valueRangeProviderRefs = "valueRange") - private TestdataValue value; - - public TestdataMixedVariablesEntity(String code, List valueList, TestdataValue value) { - super(code); - this.valueList = valueList; - this.value = value; - } - - public List getValueList() { - return valueList; - } - - public TestdataValue getValue() { - return value; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesSolution.java deleted file mode 100644 index 646a4d7432c..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/mixed/TestdataMixedVariablesSolution.java +++ /dev/null @@ -1,53 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.mixed; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.solution.PlanningEntityProperty; -import ai.timefold.solver.core.api.domain.solution.PlanningScore; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.testdomain.TestdataValue; - -@PlanningSolution -public class TestdataMixedVariablesSolution { - - public static SolutionDescriptor buildSolutionDescriptor() { - return SolutionDescriptor.buildSolutionDescriptor(TestdataMixedVariablesSolution.class, - TestdataMixedVariablesEntity.class); - } - - private List valueList; - private TestdataMixedVariablesEntity entity; - private SimpleScore score; - - @ValueRangeProvider(id = "valueRange") - @ProblemFactCollectionProperty - public List getValueList() { - return valueList; - } - - public void setValueList(List valueList) { - this.valueList = valueList; - } - - @PlanningEntityProperty - public TestdataMixedVariablesEntity getEntity() { - return entity; - } - - public void setEntity(TestdataMixedVariablesEntity entity) { - this.entity = entity; - } - - @PlanningScore - public SimpleScore getScore() { - return score; - } - - public void setScore(SimpleScore score) { - this.score = score; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedEntityEasyScoreCalculator.java similarity index 76% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedEntityEasyScoreCalculator.java index 0501e762793..a9e96ecc007 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedEntityEasyScoreCalculator.java @@ -1,15 +1,15 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; +package ai.timefold.solver.core.testdomain.mixed.multientity; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import org.jspecify.annotations.NonNull; -public class TestdataListMultiEntityEasyScoreCalculator - implements EasyScoreCalculator { +public class TestdataMixedEntityEasyScoreCalculator + implements EasyScoreCalculator { @Override - public @NonNull SimpleScore calculateScore(@NonNull TestdataListMultiEntitySolution solution) { + public @NonNull SimpleScore calculateScore(@NonNull TestdataMixedMultiEntitySolution solution) { int score = 0; for (var entity : solution.getEntityList()) { if (entity.getValueList().size() == 1) { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntity.java new file mode 100644 index 00000000000..77291e68803 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntity.java @@ -0,0 +1,43 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyComparatorClass = TestdataMixedMultiEntityFirstEntityComparator.class) +public class TestdataMixedMultiEntityFirstEntity extends TestdataObject { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange") + private List valueList; + + private int difficulty; + + public TestdataMixedMultiEntityFirstEntity() { + // Required for cloner + } + + public TestdataMixedMultiEntityFirstEntity(String code, int difficulty) { + super(code); + valueList = new ArrayList<>(); + this.difficulty = difficulty; + } + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public int getDifficulty() { + return difficulty; + } + + public void setDifficulty(int difficulty) { + this.difficulty = difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntityComparator.java new file mode 100644 index 00000000000..654b934b180 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstEntityComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import java.util.Comparator; + +public class TestdataMixedMultiEntityFirstEntityComparator implements Comparator { + @Override + public int compare(TestdataMixedMultiEntityFirstEntity v1, TestdataMixedMultiEntityFirstEntity v2) { + // ASC sort + return v1.getDifficulty() - v2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstValue.java new file mode 100644 index 00000000000..abc089f9380 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntityFirstValue.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity +public class TestdataMixedMultiEntityFirstValue extends TestdataObject { + + public TestdataMixedMultiEntityFirstValue() { + // Required for cloner + } + + public TestdataMixedMultiEntityFirstValue(String code) { + super(code); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondEntity.java new file mode 100644 index 00000000000..46286546373 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondEntity.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity +public class TestdataMixedMultiEntitySecondEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "otherValueRange", + strengthComparatorClass = TestdataMixedMultiEntitySecondValueComparator.class) + private TestdataMixedMultiEntitySecondValue basicValue; + + @PlanningVariable(valueRangeProviderRefs = "otherValueRange") + private TestdataMixedMultiEntitySecondValue secondBasicValue; + + public TestdataMixedMultiEntitySecondEntity() { + // Required for cloner + } + + public TestdataMixedMultiEntitySecondEntity(String code) { + super(code); + } + + public TestdataMixedMultiEntitySecondValue getBasicValue() { + return basicValue; + } + + public void setBasicValue(TestdataMixedMultiEntitySecondValue basicValue) { + this.basicValue = basicValue; + } + + public TestdataMixedMultiEntitySecondValue getSecondBasicValue() { + return secondBasicValue; + } + + public void setSecondBasicValue(TestdataMixedMultiEntitySecondValue secondBasicValue) { + this.secondBasicValue = secondBasicValue; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValue.java new file mode 100644 index 00000000000..cda51205e40 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValue.java @@ -0,0 +1,27 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity +public class TestdataMixedMultiEntitySecondValue extends TestdataObject { + + private int strength; + + public TestdataMixedMultiEntitySecondValue() { + // Required for cloner + } + + public TestdataMixedMultiEntitySecondValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValueComparator.java new file mode 100644 index 00000000000..8d29078d4c1 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySecondValueComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import java.util.Comparator; + +public class TestdataMixedMultiEntitySecondValueComparator implements Comparator { + @Override + public int compare(TestdataMixedMultiEntitySecondValue v1, TestdataMixedMultiEntitySecondValue v2) { + // ASC sort + return v1.getStrength() - v2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySolution.java new file mode 100644 index 00000000000..fe1c3346ca5 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/multientity/TestdataMixedMultiEntitySolution.java @@ -0,0 +1,101 @@ +package ai.timefold.solver.core.testdomain.mixed.multientity; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.solution.PlanningScore; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataMixedMultiEntitySolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor(TestdataMixedMultiEntitySolution.class, + TestdataMixedMultiEntityFirstEntity.class, TestdataMixedMultiEntitySecondEntity.class); + } + + public static TestdataMixedMultiEntitySolution generateUninitializedSolution(int entityListSize, int valueListSize, + int otherValueListSize) { + var solution = new TestdataMixedMultiEntitySolution(); + var valueList = new ArrayList(valueListSize); + var otherValueList = new ArrayList(otherValueListSize); + for (int i = 0; i < valueListSize; i++) { + valueList.add(new TestdataMixedMultiEntityFirstValue("Generated Value " + i)); + } + var strength = otherValueListSize * 10; + for (int i = 0; i < otherValueListSize; i++) { + otherValueList.add(new TestdataMixedMultiEntitySecondValue("Generated Other Value " + i, strength--)); + } + solution.setValueList(valueList); + solution.setOtherValueList(otherValueList); + var entityList = new ArrayList(entityListSize); + var otherEntityList = new ArrayList(entityListSize); + for (int i = 0; i < entityListSize; i++) { + var entity = new TestdataMixedMultiEntityFirstEntity("Entity " + i, i); + entityList.add(entity); + var otherEntity = new TestdataMixedMultiEntitySecondEntity("Other Entity " + i); + otherEntityList.add(otherEntity); + } + solution.setEntityList(entityList); + solution.setOtherEntityList(otherEntityList); + return solution; + } + + @ValueRangeProvider(id = "valueRange") + @ProblemFactCollectionProperty + private List valueList; + @ValueRangeProvider(id = "otherValueRange") + @ProblemFactCollectionProperty + private List otherValueList; + @PlanningEntityCollectionProperty + private List entityList; + @PlanningEntityCollectionProperty + private List otherEntityList; + @PlanningScore + private SimpleScore score; + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public List getOtherValueList() { + return otherValueList; + } + + public void setOtherValueList(List otherValueList) { + this.otherValueList = otherValueList; + } + + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + public List getOtherEntityList() { + return otherEntityList; + } + + public void setOtherEntityList(List otherEntityList) { + this.otherEntityList = otherEntityList; + } + + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java new file mode 100644 index 00000000000..ab936fb4656 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java @@ -0,0 +1,36 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.Iterator; +import java.util.Random; +import java.util.stream.Stream; + +import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; + +public class MixedCustomMoveIteratorFactory + implements MoveIteratorFactory> { + @Override + public long getSize(ScoreDirector scoreDirector) { + return scoreDirector.getWorkingSolution().getEntityList().size(); + } + + @Override + public Iterator> + createOriginalMoveIterator(ScoreDirector scoreDirector) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> + createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom) { + var solutionDescriptor = TestdataMixedSolution.buildSolutionDescriptor(); + var variableDescriptor = + solutionDescriptor.findEntityDescriptor(TestdataMixedEntity.class).getGenuineVariableDescriptor("basicValue"); + var move1 = new ChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().get(0), + scoreDirector.getWorkingSolution().getOtherValueList().get(0)); + var move2 = new ChangeMove<>(variableDescriptor, scoreDirector.getWorkingSolution().getEntityList().get(0), + scoreDirector.getWorkingSolution().getOtherValueList().get(1)); + return Stream.of(move1, move2).iterator(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java new file mode 100644 index 00000000000..e421db6f41a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java @@ -0,0 +1,18 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.function.BooleanSupplier; + +import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.api.solver.phase.PhaseCommand; + +public class MixedCustomPhaseCommand implements PhaseCommand { + + @Override + public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { + var moveIteratorFactory = new MixedCustomMoveIteratorFactory(); + var moveIterator = moveIteratorFactory.createRandomMoveIterator(scoreDirector, null); + var move = moveIterator.next(); + move.doMoveOnly(scoreDirector); + scoreDirector.triggerVariableListeners(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEasyScoreCalculator.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEasyScoreCalculator.java index 362ad084855..307d3f29976 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEasyScoreCalculator.java @@ -1,14 +1,14 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity; +package ai.timefold.solver.core.testdomain.mixed.singleentity; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import org.jspecify.annotations.NonNull; -public class TestdataListMultiVarEasyScoreCalculator implements EasyScoreCalculator { +public class TestdataMixedEasyScoreCalculator implements EasyScoreCalculator { @Override - public @NonNull SimpleScore calculateScore(@NonNull TestdataListMultiVarSolution solution) { + public @NonNull SimpleScore calculateScore(@NonNull TestdataMixedSolution solution) { int score = 0; for (var entity : solution.getEntityList()) { if (entity.getBasicValue() != null) { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntity.java new file mode 100644 index 00000000000..c2ae73bd492 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntity.java @@ -0,0 +1,113 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.entity.PlanningPin; +import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.api.domain.variable.ShadowVariable; +import ai.timefold.solver.core.preview.api.domain.variable.declarative.ShadowSources; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyComparatorClass = TestdataMixedEntityComparator.class) +public class TestdataMixedEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "otherValueRange", + strengthComparatorClass = TestdataMixedOtherValueComparator.class) + private TestdataMixedOtherValue basicValue; + + @PlanningVariable(valueRangeProviderRefs = "otherValueRange") + private TestdataMixedOtherValue secondBasicValue; + + @PlanningListVariable(valueRangeProviderRefs = "valueRange") + private List valueList; + + @PlanningPin + private boolean pinned = false; + + @PlanningPinToIndex + private int pinnedIndex = 0; + + private int difficulty; + + @ShadowVariable(supplierName = "updateDeclarativeShadowValue") + private Integer declarativeShadowVariableValue; + + public TestdataMixedEntity() { + // Required for cloner + } + + public TestdataMixedEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + valueList = new ArrayList<>(); + } + + public TestdataMixedOtherValue getBasicValue() { + return basicValue; + } + + public void setBasicValue(TestdataMixedOtherValue basicValue) { + this.basicValue = basicValue; + } + + public TestdataMixedOtherValue getSecondBasicValue() { + return secondBasicValue; + } + + public void setSecondBasicValue(TestdataMixedOtherValue secondBasicValue) { + this.secondBasicValue = secondBasicValue; + } + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public boolean isPinned() { + return pinned; + } + + public void setPinned(boolean pinned) { + this.pinned = pinned; + } + + public int getPinnedIndex() { + return pinnedIndex; + } + + public void setPinnedIndex(int pinnedIndex) { + this.pinnedIndex = pinnedIndex; + } + + public int getDifficulty() { + return difficulty; + } + + public Integer getDeclarativeShadowVariableValue() { + return declarativeShadowVariableValue; + } + + public void setDeclarativeShadowVariableValue(Integer declarativeShadowVariableValue) { + this.declarativeShadowVariableValue = declarativeShadowVariableValue; + } + + public void setDifficulty(int difficulty) { + this.difficulty = difficulty; + + } + + @ShadowSources("basicValue") + public Integer updateDeclarativeShadowValue() { + if (basicValue != null) { + return basicValue.getStrength(); + } + return null; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntityComparator.java new file mode 100644 index 00000000000..ad65f3644da --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedEntityComparator.java @@ -0,0 +1,10 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.Comparator; + +public class TestdataMixedEntityComparator implements Comparator { + @Override + public int compare(TestdataMixedEntity v1, TestdataMixedEntity v2) { + return v1.getDifficulty() - v2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValue.java new file mode 100644 index 00000000000..b5d5afe62ec --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValue.java @@ -0,0 +1,64 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable; +import ai.timefold.solver.core.api.domain.variable.ShadowVariable; +import ai.timefold.solver.core.preview.api.domain.variable.declarative.ShadowSources; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity +public class TestdataMixedOtherValue extends TestdataObject { + + @InverseRelationShadowVariable(sourceVariableName = "basicValue") + private List entityList; + + @ShadowVariable(supplierName = "updateDeclarativeShadowValue") + private Integer declarativeShadowVariableValue; + + private int strength; + + public TestdataMixedOtherValue() { + // Required for cloner + } + + public TestdataMixedOtherValue(String code, int strength) { + super(code); + this.strength = strength; + entityList = new ArrayList<>(); + } + + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + public Integer getDeclarativeShadowVariableValue() { + return declarativeShadowVariableValue; + } + + public void setDeclarativeShadowVariableValue(Integer declarativeShadowVariableValue) { + this.declarativeShadowVariableValue = declarativeShadowVariableValue; + } + + @ShadowSources("entityList") + public Integer updateDeclarativeShadowValue() { + if (entityList != null) { + return entityList.size() + 2; + } + return null; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValueComparator.java new file mode 100644 index 00000000000..ed83ce54d1d --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedOtherValueComparator.java @@ -0,0 +1,10 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import java.util.Comparator; + +public class TestdataMixedOtherValueComparator implements Comparator { + @Override + public int compare(TestdataMixedOtherValue o1, TestdataMixedOtherValue o2) { + return o1.getStrength() - o2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedSolution.java new file mode 100644 index 00000000000..e03580d65de --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedSolution.java @@ -0,0 +1,88 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import static ai.timefold.solver.core.config.solver.PreviewFeature.DECLARATIVE_SHADOW_VARIABLES; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.solution.PlanningScore; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataMixedSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor(Set.of(DECLARATIVE_SHADOW_VARIABLES), TestdataMixedSolution.class, + TestdataMixedEntity.class, TestdataMixedValue.class, TestdataMixedOtherValue.class); + } + + public static TestdataMixedSolution generateUninitializedSolution(int entityListSize, int valueListSize, + int otherValueListSize) { + var solution = new TestdataMixedSolution(); + var valueList = new ArrayList(valueListSize); + var otherValueList = new ArrayList(otherValueListSize); + for (int i = 0; i < valueListSize; i++) { + valueList.add(new TestdataMixedValue("Generated Value " + i)); + } + for (int i = 0; i < otherValueListSize; i++) { + otherValueList.add(new TestdataMixedOtherValue("Generated Other Value " + i, valueListSize - i)); + } + solution.setValueList(valueList); + solution.setOtherValueList(otherValueList); + var entityList = new ArrayList(entityListSize); + for (int i = 0; i < entityListSize; i++) { + var entity = new TestdataMixedEntity("Entity " + i, i); + entityList.add(entity); + } + solution.setEntityList(entityList); + return solution; + } + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueList; + @ValueRangeProvider(id = "otherValueRange") + @PlanningEntityCollectionProperty + private List otherValueList; + @PlanningEntityCollectionProperty + private List entityList; + @PlanningScore + private SimpleScore score; + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public List getOtherValueList() { + return otherValueList; + } + + public void setOtherValueList(List otherValueList) { + this.otherValueList = otherValueList; + } + + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedValue.java new file mode 100644 index 00000000000..0e9f70d2632 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedValue.java @@ -0,0 +1,116 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.variable.CascadingUpdateShadowVariable; +import ai.timefold.solver.core.api.domain.variable.IndexShadowVariable; +import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable; +import ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable; +import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable; +import ai.timefold.solver.core.api.domain.variable.ShadowVariable; +import ai.timefold.solver.core.preview.api.domain.variable.declarative.ShadowSources; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity +public class TestdataMixedValue extends TestdataObject { + + @InverseRelationShadowVariable(sourceVariableName = "valueList") + private TestdataMixedEntity entity; + + @PreviousElementShadowVariable(sourceVariableName = "valueList") + private TestdataMixedValue previousElement; + + @NextElementShadowVariable(sourceVariableName = "valueList") + private TestdataMixedValue nextElement; + + @IndexShadowVariable(sourceVariableName = "valueList") + private Integer index; + + @ShadowVariable(variableListenerClass = TestdataMixedVariableListener.class, sourceVariableName = "index") + private Integer shadowVariableListenerValue; + + @CascadingUpdateShadowVariable(targetMethodName = "updateCascadingShadowValue") + private Integer cascadingShadowVariableValue; + + @ShadowVariable(supplierName = "updateDeclarativeShadowValue") + private Integer declarativeShadowVariableValue; + + public TestdataMixedValue() { + // Required for cloner + } + + public TestdataMixedValue(String code) { + super(code); + } + + public TestdataMixedEntity getEntity() { + return entity; + } + + public void setEntity(TestdataMixedEntity entity) { + this.entity = entity; + } + + public TestdataMixedValue getPreviousElement() { + return previousElement; + } + + public void setPreviousElement(TestdataMixedValue previousElement) { + this.previousElement = previousElement; + } + + public TestdataMixedValue getNextElement() { + return nextElement; + } + + public void setNextElement(TestdataMixedValue nextElement) { + this.nextElement = nextElement; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public Integer getShadowVariableListenerValue() { + return shadowVariableListenerValue; + } + + public void setShadowVariableListenerValue(Integer shadowVariableListenerValue) { + this.shadowVariableListenerValue = shadowVariableListenerValue; + } + + public Integer getCascadingShadowVariableValue() { + return cascadingShadowVariableValue; + } + + public void setCascadingShadowVariableValue(Integer cascadingShadowVariableValue) { + this.cascadingShadowVariableValue = cascadingShadowVariableValue; + } + + public Integer getDeclarativeShadowVariableValue() { + return declarativeShadowVariableValue; + } + + public void setDeclarativeShadowVariableValue(Integer declarativeShadowVariableValue) { + this.declarativeShadowVariableValue = declarativeShadowVariableValue; + } + + public void updateCascadingShadowValue() { + if (index != null) { + this.cascadingShadowVariableValue = index + 1; + } else { + this.cascadingShadowVariableValue = null; + } + } + + @ShadowSources("index") + public Integer updateDeclarativeShadowValue() { + if (index != null) { + return index + 2; + } + return null; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedVariableListener.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedVariableListener.java new file mode 100644 index 00000000000..3153a145dba --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/TestdataMixedVariableListener.java @@ -0,0 +1,44 @@ +package ai.timefold.solver.core.testdomain.mixed.singleentity; + +import ai.timefold.solver.core.api.domain.variable.VariableListener; +import ai.timefold.solver.core.api.score.director.ScoreDirector; + +import org.jspecify.annotations.NonNull; + +public class TestdataMixedVariableListener implements VariableListener { + @Override + public void beforeVariableChanged(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + // Ignore + } + + @Override + public void afterVariableChanged(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + value.setShadowVariableListenerValue(value.getIndex()); + } + + @Override + public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + // Ignore + } + + @Override + public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + // Ignore + } + + @Override + public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + // Ignore + } + + @Override + public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, + @NonNull TestdataMixedValue value) { + // Ignore + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEasyScoreCalculator.java similarity index 74% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEasyScoreCalculator.java index 6fa8ab013d7..cca7cc74ba9 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEasyScoreCalculator.java @@ -1,15 +1,15 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar; +package ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import org.jspecify.annotations.NonNull; -public class TestdataUnassignedListMultiVarEasyScoreCalculator - implements EasyScoreCalculator { +public class TestdataUnassignedMixedEasyScoreCalculator + implements EasyScoreCalculator { @Override - public @NonNull SimpleScore calculateScore(@NonNull TestdataUnassignedListMultiVarSolution solution) { + public @NonNull SimpleScore calculateScore(@NonNull TestdataUnassignedMixedSolution solution) { int score = 0; for (var entity : solution.getEntityList()) { if (entity.getBasicValue() != null && !entity.getBasicValue().isBlocked()) { @@ -20,7 +20,7 @@ public class TestdataUnassignedListMultiVarEasyScoreCalculator if (entity.getSecondBasicValue() != null) { score++; } - if (entity.getValueList().stream().anyMatch(TestdataUnassignedListMultiVarValue::isBlocked)) { + if (entity.getValueList().stream().anyMatch(TestdataUnassignedMixedValue::isBlocked)) { score -= 10; } else if (entity.getValueList().size() == 3) { score += 2; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEntity.java similarity index 61% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEntity.java index 8c698439bd4..17707539f52 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar; +package ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar; import java.util.ArrayList; import java.util.List; @@ -11,16 +11,16 @@ import ai.timefold.solver.core.testdomain.TestdataObject; @PlanningEntity -public class TestdataUnassignedListMultiVarEntity extends TestdataObject { +public class TestdataUnassignedMixedEntity extends TestdataObject { @PlanningVariable(valueRangeProviderRefs = "otherValueRange", allowsUnassigned = true) - private TestdataUnassignedListMultiVarOtherValue basicValue; + private TestdataUnassignedMixedOtherValue basicValue; @PlanningVariable(valueRangeProviderRefs = "otherValueRange") - private TestdataUnassignedListMultiVarOtherValue secondBasicValue; + private TestdataUnassignedMixedOtherValue secondBasicValue; @PlanningListVariable(valueRangeProviderRefs = "valueRange", allowsUnassignedValues = true) - private List valueList; + private List valueList; @PlanningPin private boolean pinned = false; @@ -28,36 +28,36 @@ public class TestdataUnassignedListMultiVarEntity extends TestdataObject { @PlanningPinToIndex private int pinnedIndex = 0; - public TestdataUnassignedListMultiVarEntity() { + public TestdataUnassignedMixedEntity() { // Required for cloner } - public TestdataUnassignedListMultiVarEntity(String code) { + public TestdataUnassignedMixedEntity(String code) { super(code); valueList = new ArrayList<>(); } - public TestdataUnassignedListMultiVarOtherValue getBasicValue() { + public TestdataUnassignedMixedOtherValue getBasicValue() { return basicValue; } - public void setBasicValue(TestdataUnassignedListMultiVarOtherValue basicValue) { + public void setBasicValue(TestdataUnassignedMixedOtherValue basicValue) { this.basicValue = basicValue; } - public TestdataUnassignedListMultiVarOtherValue getSecondBasicValue() { + public TestdataUnassignedMixedOtherValue getSecondBasicValue() { return secondBasicValue; } - public void setSecondBasicValue(TestdataUnassignedListMultiVarOtherValue secondBasicValue) { + public void setSecondBasicValue(TestdataUnassignedMixedOtherValue secondBasicValue) { this.secondBasicValue = secondBasicValue; } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedOtherValue.java similarity index 53% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedOtherValue.java index a6a64c461ac..b9f26eba811 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedOtherValue.java @@ -1,16 +1,16 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar; +package ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataUnassignedListMultiVarValue extends TestdataObject { +public class TestdataUnassignedMixedOtherValue extends TestdataObject { private boolean blocked = false; - public TestdataUnassignedListMultiVarValue() { + public TestdataUnassignedMixedOtherValue() { // Required for cloner } - public TestdataUnassignedListMultiVarValue(String code) { + public TestdataUnassignedMixedOtherValue(String code) { super(code); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedSolution.java similarity index 51% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedSolution.java index 491a4d903b8..6654ca7b2f7 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity; +package ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar; import java.util.ArrayList; import java.util.List; @@ -12,29 +12,29 @@ import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @PlanningSolution -public class TestdataListMultiVarSolution { +public class TestdataUnassignedMixedSolution { - public static SolutionDescriptor buildSolutionDescriptor() { - return SolutionDescriptor.buildSolutionDescriptor(TestdataListMultiVarSolution.class, TestdataListMultiVarEntity.class, - TestdataListMultiVarValue.class, TestdataListMultiVarOtherValue.class); + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor(TestdataUnassignedMixedSolution.class, + TestdataUnassignedMixedEntity.class); } - public static TestdataListMultiVarSolution generateUninitializedSolution(int entityListSize, int valueListSize, + public static TestdataUnassignedMixedSolution generateUninitializedSolution(int entityListSize, int valueListSize, int otherValueListSize) { - var solution = new TestdataListMultiVarSolution(); - var valueList = new ArrayList(valueListSize); - var otherValueList = new ArrayList(otherValueListSize); + var solution = new TestdataUnassignedMixedSolution(); + var valueList = new ArrayList(valueListSize); + var otherValueList = new ArrayList(otherValueListSize); for (int i = 0; i < valueListSize; i++) { - valueList.add(new TestdataListMultiVarValue("Generated Value " + i)); + valueList.add(new TestdataUnassignedMixedValue("Generated Value " + i)); } for (int i = 0; i < otherValueListSize; i++) { - otherValueList.add(new TestdataListMultiVarOtherValue("Generated Other Value " + i)); + otherValueList.add(new TestdataUnassignedMixedOtherValue("Generated Other Value " + i)); } solution.setValueList(valueList); solution.setOtherValueList(otherValueList); - var entityList = new ArrayList(entityListSize); + var entityList = new ArrayList(entityListSize); for (int i = 0; i < entityListSize; i++) { - var entity = new TestdataListMultiVarEntity("Entity " + i); + var entity = new TestdataUnassignedMixedEntity("Entity " + i); entityList.add(entity); } solution.setEntityList(entityList); @@ -43,36 +43,36 @@ public static TestdataListMultiVarSolution generateUninitializedSolution(int ent @ValueRangeProvider(id = "valueRange") @ProblemFactCollectionProperty - private List valueList; + private List valueList; @ValueRangeProvider(id = "otherValueRange") @ProblemFactCollectionProperty - private List otherValueList; + private List otherValueList; @PlanningEntityCollectionProperty - private List entityList; + private List entityList; @PlanningScore private SimpleScore score; - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } - public List getOtherValueList() { + public List getOtherValueList() { return otherValueList; } - public void setOtherValueList(List otherValueList) { + public void setOtherValueList(List otherValueList) { this.otherValueList = otherValueList; } - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarOtherValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedValue.java similarity index 52% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarOtherValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedValue.java index 34e5dfa88a5..b6fba336ebb 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarOtherValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/unassignedvar/TestdataUnassignedMixedValue.java @@ -1,16 +1,16 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar; +package ai.timefold.solver.core.testdomain.mixed.singleentity.unassignedvar; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataUnassignedListMultiVarOtherValue extends TestdataObject { +public class TestdataUnassignedMixedValue extends TestdataObject { private boolean blocked = false; - public TestdataUnassignedListMultiVarOtherValue() { + public TestdataUnassignedMixedValue() { // Required for cloner } - public TestdataUnassignedListMultiVarOtherValue(String code) { + public TestdataUnassignedMixedValue(String code) { super(code); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multientity/TestdataMultiEntitySolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multientity/TestdataMultiEntitySolution.java index 578b6e69bb9..a5f1cf4840b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multientity/TestdataMultiEntitySolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/multientity/TestdataMultiEntitySolution.java @@ -1,5 +1,6 @@ package ai.timefold.solver.core.testdomain.multientity; +import java.util.ArrayList; import java.util.List; import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; @@ -20,6 +21,24 @@ public static SolutionDescriptor buildSolutionDescr TestdataLeadEntity.class, TestdataHerdEntity.class); } + public static TestdataMultiEntitySolution generateUninitializedSolution(int entityListSize, int valueListSize) { + var solution = new TestdataMultiEntitySolution(); + var valueList = new ArrayList(valueListSize); + for (int i = 0; i < valueListSize; i++) { + valueList.add(new TestdataValue("Generated Value " + i)); + } + solution.setValueList(valueList); + var entityLeadList = new ArrayList(entityListSize); + var entityHerdList = new ArrayList(entityListSize); + for (int i = 0; i < entityListSize; i++) { + entityLeadList.add(new TestdataLeadEntity("LeadEntity " + i)); + entityHerdList.add(new TestdataHerdEntity("HerdEntity " + i)); + } + solution.setLeadEntityList(entityLeadList); + solution.setHerdEntityList(entityHerdList); + return solution; + } + private List valueList; private List leadEntityList; private List herdEntityList; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarEntity.java similarity index 97% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarEntity.java index 61e9a3c74f7..7ddf763da91 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.multivar.basic; +package ai.timefold.solver.core.testdomain.multivar; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarSolution.java similarity index 70% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarSolution.java index 53f44505d59..96b8af4345a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultiVarSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarSolution.java @@ -1,5 +1,6 @@ -package ai.timefold.solver.core.testdomain.multivar.basic; +package ai.timefold.solver.core.testdomain.multivar; +import java.util.ArrayList; import java.util.List; import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; @@ -19,6 +20,25 @@ public static SolutionDescriptor buildSolutionDescript return SolutionDescriptor.buildSolutionDescriptor(TestdataMultiVarSolution.class, TestdataMultiVarEntity.class); } + public static TestdataMultiVarSolution generateUninitializedSolution(int entityListSize, int valueListSize) { + var solution = new TestdataMultiVarSolution(); + var valueList = new ArrayList(valueListSize); + var otherValueList = new ArrayList(valueListSize); + for (int i = 0; i < valueListSize; i++) { + valueList.add(new TestdataValue("Generated Value " + i)); + otherValueList.add(new TestdataOtherValue("Generated Other Value " + i)); + } + solution.setValueList(valueList); + solution.setOtherValueList(otherValueList); + var entityList = new ArrayList(entityListSize); + for (int i = 0; i < entityListSize; i++) { + var entity = new TestdataMultiVarEntity("Entity " + i); + entityList.add(entity); + } + solution.setMultiVarEntityList(entityList); + return solution; + } + private List valueList; private List otherValueList; private List multiVarEntityList; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultivarIncrementalScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultivarIncrementalScoreCalculator.java similarity index 98% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultivarIncrementalScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultivarIncrementalScoreCalculator.java index 7c94edf2f32..3ed0430cc13 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataMultivarIncrementalScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultivarIncrementalScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.multivar.basic; +package ai.timefold.solver.core.testdomain.multivar; import java.util.Collection; import java.util.Collections; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataOtherValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataOtherValue.java similarity index 79% rename from core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataOtherValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataOtherValue.java index eb2b19a3694..244c2f95c69 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/basic/TestdataOtherValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataOtherValue.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.multivar.basic; +package ai.timefold.solver.core.testdomain.multivar; import ai.timefold.solver.core.testdomain.TestdataObject; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstEntity.java deleted file mode 100644 index 389f500d2b8..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiEntityFirstEntity extends TestdataObject { - - @PlanningListVariable(valueRangeProviderRefs = "valueRange") - private List valueList; - - public TestdataListMultiEntityFirstEntity() { - // Required for cloner - } - - public TestdataListMultiEntityFirstEntity(String code) { - super(code); - valueList = new ArrayList<>(); - } - - public List getValueList() { - return valueList; - } - - public void setValueList(List valueList) { - this.valueList = valueList; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstValue.java deleted file mode 100644 index 529874f6045..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntityFirstValue.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiEntityFirstValue extends TestdataObject { - - public TestdataListMultiEntityFirstValue() { - // Required for cloner - } - - public TestdataListMultiEntityFirstValue(String code) { - super(code); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondEntity.java deleted file mode 100644 index 8f6acbb79f5..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondEntity.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiEntitySecondEntity extends TestdataObject { - - @PlanningVariable(valueRangeProviderRefs = "otherValueRange") - private TestdataListMultiEntitySecondValue basicValue; - - @PlanningVariable(valueRangeProviderRefs = "otherValueRange") - private TestdataListMultiEntitySecondValue secondBasicValue; - - public TestdataListMultiEntitySecondEntity() { - // Required for cloner - } - - public TestdataListMultiEntitySecondEntity(String code) { - super(code); - } - - public TestdataListMultiEntitySecondValue getBasicValue() { - return basicValue; - } - - public void setBasicValue(TestdataListMultiEntitySecondValue basicValue) { - this.basicValue = basicValue; - } - - public TestdataListMultiEntitySecondValue getSecondBasicValue() { - return secondBasicValue; - } - - public void setSecondBasicValue(TestdataListMultiEntitySecondValue secondBasicValue) { - this.secondBasicValue = secondBasicValue; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondValue.java deleted file mode 100644 index caf87e234aa..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySecondValue.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiEntitySecondValue extends TestdataObject { - - public TestdataListMultiEntitySecondValue() { - // Required for cloner - } - - public TestdataListMultiEntitySecondValue(String code) { - super(code); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySolution.java deleted file mode 100644 index 4edb6904c04..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/multientity/TestdataListMultiEntitySolution.java +++ /dev/null @@ -1,94 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.multientity; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; -import ai.timefold.solver.core.api.domain.solution.PlanningScore; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; - -@PlanningSolution -public class TestdataListMultiEntitySolution { - - public static TestdataListMultiEntitySolution generateUninitializedSolution(int entityListSize, int valueListSize, - int otherValueListSize) { - var solution = new TestdataListMultiEntitySolution(); - var valueList = new ArrayList(valueListSize); - var otherValueList = new ArrayList(otherValueListSize); - for (int i = 0; i < valueListSize; i++) { - valueList.add(new TestdataListMultiEntityFirstValue("Generated Value " + i)); - } - for (int i = 0; i < otherValueListSize; i++) { - otherValueList.add(new TestdataListMultiEntitySecondValue("Generated Other Value " + i)); - } - solution.setValueList(valueList); - solution.setOtherValueList(otherValueList); - var entityList = new ArrayList(entityListSize); - var otherEntityList = new ArrayList(entityListSize); - for (int i = 0; i < entityListSize; i++) { - var entity = new TestdataListMultiEntityFirstEntity("Entity " + i); - entityList.add(entity); - var otherEntity = new TestdataListMultiEntitySecondEntity("Other Entity " + i); - otherEntityList.add(otherEntity); - } - solution.setEntityList(entityList); - solution.setOtherEntityList(otherEntityList); - return solution; - } - - @ValueRangeProvider(id = "valueRange") - @ProblemFactCollectionProperty - private List valueList; - @ValueRangeProvider(id = "otherValueRange") - @ProblemFactCollectionProperty - private List otherValueList; - @PlanningEntityCollectionProperty - private List entityList; - @PlanningEntityCollectionProperty - private List otherEntityList; - @PlanningScore - private SimpleScore score; - - public List getValueList() { - return valueList; - } - - public void setValueList(List valueList) { - this.valueList = valueList; - } - - public List getOtherValueList() { - return otherValueList; - } - - public void setOtherValueList(List otherValueList) { - this.otherValueList = otherValueList; - } - - public List getEntityList() { - return entityList; - } - - public void setEntityList(List entityList) { - this.entityList = entityList; - } - - public List getOtherEntityList() { - return otherEntityList; - } - - public void setOtherEntityList(List otherEntityList) { - this.otherEntityList = otherEntityList; - } - - public SimpleScore getScore() { - return score; - } - - public void setScore(SimpleScore score) { - this.score = score; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEntity.java deleted file mode 100644 index 4847ea19a28..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarEntity.java +++ /dev/null @@ -1,79 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.entity.PlanningPin; -import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex; -import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiVarEntity extends TestdataObject { - - @PlanningVariable(valueRangeProviderRefs = "otherValueRange") - private TestdataListMultiVarOtherValue basicValue; - - @PlanningVariable(valueRangeProviderRefs = "otherValueRange") - private TestdataListMultiVarOtherValue secondBasicValue; - - @PlanningListVariable(valueRangeProviderRefs = "valueRange") - private List valueList; - - @PlanningPin - private boolean pinned = false; - - @PlanningPinToIndex - private int pinnedIndex = 0; - - public TestdataListMultiVarEntity() { - // Required for cloner - } - - public TestdataListMultiVarEntity(String code) { - super(code); - valueList = new ArrayList<>(); - } - - public TestdataListMultiVarOtherValue getBasicValue() { - return basicValue; - } - - public void setBasicValue(TestdataListMultiVarOtherValue basicValue) { - this.basicValue = basicValue; - } - - public TestdataListMultiVarOtherValue getSecondBasicValue() { - return secondBasicValue; - } - - public void setSecondBasicValue(TestdataListMultiVarOtherValue secondBasicValue) { - this.secondBasicValue = secondBasicValue; - } - - public List getValueList() { - return valueList; - } - - public void setValueList(List valueList) { - this.valueList = valueList; - } - - public boolean isPinned() { - return pinned; - } - - public void setPinned(boolean pinned) { - this.pinned = pinned; - } - - public int getPinnedIndex() { - return pinnedIndex; - } - - public void setPinnedIndex(int pinnedIndex) { - this.pinnedIndex = pinnedIndex; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarOtherValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarOtherValue.java deleted file mode 100644 index 8740cdeaa62..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarOtherValue.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiVarOtherValue extends TestdataObject { - - @InverseRelationShadowVariable(sourceVariableName = "basicValue") - private List entityList; - - public TestdataListMultiVarOtherValue() { - // Required for cloner - } - - public TestdataListMultiVarOtherValue(String code) { - super(code); - entityList = new ArrayList<>(); - } - - public List getEntityList() { - return entityList; - } - - public void setEntityList(List entityList) { - this.entityList = entityList; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarValue.java deleted file mode 100644 index aef299201e2..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/TestdataListMultiVarValue.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.variable.IndexShadowVariable; -import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable; -import ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable; -import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity -public class TestdataListMultiVarValue extends TestdataObject { - - @InverseRelationShadowVariable(sourceVariableName = "valueList") - private TestdataListMultiVarEntity entity; - - @PreviousElementShadowVariable(sourceVariableName = "valueList") - private TestdataListMultiVarValue previousElement; - - @NextElementShadowVariable(sourceVariableName = "valueList") - private TestdataListMultiVarValue nextElement; - - @IndexShadowVariable(sourceVariableName = "valueList") - private Integer index; - - public TestdataListMultiVarValue() { - // Required for cloner - } - - public TestdataListMultiVarValue(String code) { - super(code); - } - - public TestdataListMultiVarEntity getEntity() { - return entity; - } - - public void setEntity(TestdataListMultiVarEntity entity) { - this.entity = entity; - } - - public TestdataListMultiVarValue getPreviousElement() { - return previousElement; - } - - public void setPreviousElement(TestdataListMultiVarValue previousElement) { - this.previousElement = previousElement; - } - - public TestdataListMultiVarValue getNextElement() { - return nextElement; - } - - public void setNextElement(TestdataListMultiVarValue nextElement) { - this.nextElement = nextElement; - } - - public Integer getIndex() { - return index; - } - - public void setIndex(Integer index) { - this.index = index; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarSolution.java deleted file mode 100644 index ad9a8bcbb1f..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/multivar/list/singleentity/unassignedvar/TestdataUnassignedListMultiVarSolution.java +++ /dev/null @@ -1,86 +0,0 @@ -package ai.timefold.solver.core.testdomain.multivar.list.singleentity.unassignedvar; - -import java.util.ArrayList; -import java.util.List; - -import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; -import ai.timefold.solver.core.api.domain.solution.PlanningScore; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; - -@PlanningSolution -public class TestdataUnassignedListMultiVarSolution { - - public static SolutionDescriptor buildSolutionDescriptor() { - return SolutionDescriptor.buildSolutionDescriptor(TestdataUnassignedListMultiVarSolution.class, - TestdataUnassignedListMultiVarEntity.class); - } - - public static TestdataUnassignedListMultiVarSolution generateUninitializedSolution(int entityListSize, int valueListSize, - int otherValueListSize) { - var solution = new TestdataUnassignedListMultiVarSolution(); - var valueList = new ArrayList(valueListSize); - var otherValueList = new ArrayList(otherValueListSize); - for (int i = 0; i < valueListSize; i++) { - valueList.add(new TestdataUnassignedListMultiVarValue("Generated Value " + i)); - } - for (int i = 0; i < otherValueListSize; i++) { - otherValueList.add(new TestdataUnassignedListMultiVarOtherValue("Generated Other Value " + i)); - } - solution.setValueList(valueList); - solution.setOtherValueList(otherValueList); - var entityList = new ArrayList(entityListSize); - for (int i = 0; i < entityListSize; i++) { - var entity = new TestdataUnassignedListMultiVarEntity("Entity " + i); - entityList.add(entity); - } - solution.setEntityList(entityList); - return solution; - } - - @ValueRangeProvider(id = "valueRange") - @ProblemFactCollectionProperty - private List valueList; - @ValueRangeProvider(id = "otherValueRange") - @ProblemFactCollectionProperty - private List otherValueList; - @PlanningEntityCollectionProperty - private List entityList; - @PlanningScore - private SimpleScore score; - - public List getValueList() { - return valueList; - } - - public void setValueList(List valueList) { - this.valueList = valueList; - } - - public List getOtherValueList() { - return otherValueList; - } - - public void setOtherValueList(List otherValueList) { - this.otherValueList = otherValueList; - } - - public List getEntityList() { - return entityList; - } - - public void setEntityList(List entityList) { - this.entityList = entityList; - } - - public SimpleScore getScore() { - return score; - } - - public void setScore(SimpleScore score) { - this.score = score; - } -}