From 6a0f4299a592674686d6b2a74be12bcea68af92f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 24 Sep 2025 09:40:17 -0300 Subject: [PATCH 01/33] chore: update factory class for basic variables --- .../domain/common/SorterWeightFactory.java | 31 +++++++++++++++++++ .../api/domain/variable/PlanningVariable.java | 8 ++--- .../descriptor/GenuineVariableDescriptor.java | 7 +++-- .../SelectionSorterWeightFactory.java | 11 ++----- .../WeightFactorySelectionSorter.java | 7 +++-- 5 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java new file mode 100644 index 0000000000..9a669f5c3c --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java @@ -0,0 +1,31 @@ +package ai.timefold.solver.core.api.domain.common; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.impl.heuristic.move.Move; +import ai.timefold.solver.core.impl.heuristic.selector.Selector; + +/** + * Creates a weight to decide the order of a collections of selections + * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). + * The selections are then sorted by their weight, + * normally ascending unless it's configured descending. + * + *

+ * Implementations are expected to be stateless. + * The solver may choose to reuse instances. + * + * @param the solution type, the class with the {@link PlanningSolution} annotation + * @param the selection type + */ +@FunctionalInterface +public interface SorterWeightFactory { + + /** + * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to + * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} + * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} + */ + Comparable createSorterWeight(Solution_ solution, T selection); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 501ab44064..6632772d16 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -8,10 +8,10 @@ import java.lang.annotation.Target; import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms. @@ -88,17 +88,17 @@ interface NullStrengthComparator extends Comparator { } /** - * The {@link SelectionSorterWeightFactory} alternative for {@link #strengthComparatorClass()}. + * The {@link SorterWeightFactory} alternative for {@link #strengthComparatorClass()}. *

* Do not use together with {@link #strengthComparatorClass()}. * * @return {@link NullStrengthWeightFactory} when it is null (workaround for annotation limitation) * @see #strengthComparatorClass() */ - Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; + Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ - interface NullStrengthWeightFactory extends SelectionSorterWeightFactory { + interface NullStrengthWeightFactory extends SorterWeightFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 28d1bc48be..7a8caee360 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -8,6 +8,7 @@ import java.util.Comparator; import java.util.stream.Stream; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; @@ -21,7 +22,6 @@ import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; /** @@ -155,8 +155,9 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli } } + @SuppressWarnings("rawtypes") protected void processStrength(Class strengthComparatorClass, - Class strengthWeightFactoryClass) { + Class strengthWeightFactoryClass) { if (strengthComparatorClass == PlanningVariable.NullStrengthComparator.class) { strengthComparatorClass = null; } @@ -179,7 +180,7 @@ protected void processStrength(Class strengthComparatorCla SelectionSorterOrder.DESCENDING); } if (strengthWeightFactoryClass != null) { - SelectionSorterWeightFactory strengthWeightFactory = newInstance(this::toString, + SorterWeightFactory strengthWeightFactory = newInstance(this::toString, "strengthWeightFactoryClass", strengthWeightFactoryClass); increasingStrengthSorter = new WeightFactorySelectionSorter<>(strengthWeightFactory, SelectionSorterOrder.ASCENDING); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 4cbfa2157e..98622f2589 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -1,5 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.heuristic.move.Move; @@ -18,14 +19,8 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ +@Deprecated(forRemoval = true, since = "1.27.0") @FunctionalInterface -public interface SelectionSorterWeightFactory { - - /** - * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to - * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} - * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} - */ - Comparable createSorterWeight(Solution_ solution, T selection); +public interface SelectionSorterWeightFactory extends SorterWeightFactory { } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java index b91962141f..85445fa75d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java @@ -7,6 +7,7 @@ import java.util.SortedMap; import java.util.TreeMap; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; @@ -15,17 +16,17 @@ import ai.timefold.solver.core.impl.heuristic.selector.Selector; /** - * Sorts a selection {@link List} based on a {@link SelectionSorterWeightFactory}. + * Sorts a selection {@link List} based on a {@link SorterWeightFactory}. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ public final class WeightFactorySelectionSorter implements SelectionSorter { - private final SelectionSorterWeightFactory selectionSorterWeightFactory; + private final SorterWeightFactory selectionSorterWeightFactory; private final Comparator appliedWeightComparator; - public WeightFactorySelectionSorter(SelectionSorterWeightFactory selectionSorterWeightFactory, + public WeightFactorySelectionSorter(SorterWeightFactory selectionSorterWeightFactory, SelectionSorterOrder selectionSorterOrder) { this.selectionSorterWeightFactory = selectionSorterWeightFactory; switch (selectionSorterOrder) { From b77045f3013374698084ef8e8167b13f0a422330 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 24 Sep 2025 10:29:03 -0300 Subject: [PATCH 02/33] chore: update factory class for planning entities and planning values --- core/src/build/revapi-differences.json | 30 +++++++++++++++++++ .../api/domain/entity/PlanningEntity.java | 8 ++--- .../selector/entity/EntitySelectorConfig.java | 10 +++---- .../selector/value/ValueSelectorConfig.java | 10 +++---- .../entity/EntitySelectorFactory.java | 4 +-- .../selector/value/ValueSelectorFactory.java | 4 +-- .../WeightFactorySelectionSorterTest.java | 5 ++-- .../entity/EntitySelectorFactoryTest.java | 4 +-- .../value/ValueSelectorFactoryTest.java | 4 +-- .../TestdataDifficultyWeightFactory.java | 4 +-- .../optimization-algorithms/overview.adoc | 10 +++---- 11 files changed, 62 insertions(+), 31 deletions(-) diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index f122c49e3e..729accf328 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -348,6 +348,36 @@ "oldValue": "{\"constructionHeuristicType\", \"entitySorterManner\", \"valueSorterManner\", \"entityPlacerConfig\", \"moveSelectorConfigList\", \"foragerConfig\"}", "newValue": "{\"constructionHeuristicType\", \"entitySorterManner\", \"valueSorterManner\", \"entityPlacerConfigList\", \"moveSelectorConfigList\", \"foragerConfig\"}", "justification": "New CH configuration with multiple placers" + }, + { + "ignore": true, + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", + "justification": "New weight factory base class" + }, + { + "ignore": true, + "code": "java.method.parameterTypeParameterChanged", + "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "parameterIndex": "0", + "justification": "New weight factory base class" + }, + { + "ignore": true, + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", + "justification": "New weight factory base class" + }, + { + "ignore": true, + "code": "java.method.parameterTypeParameterChanged", + "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "parameterIndex": "0", + "justification": "New weight factory base class" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index ab0e2f9ea2..a53f6bb3f7 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -7,9 +7,9 @@ import java.lang.annotation.Target; import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that the class is a planning entity. @@ -79,17 +79,17 @@ interface NullDifficultyComparator extends Comparator { } /** - * The {@link SelectionSorterWeightFactory} alternative for {@link #difficultyComparatorClass()}. + * The {@link SorterWeightFactory} alternative for {@link #difficultyComparatorClass()}. *

* Do not use together with {@link #difficultyComparatorClass()}. * * @return {@link NullDifficultyWeightFactory} when it is null (workaround for annotation limitation) * @see #difficultyComparatorClass() */ - Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; + Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */ - interface NullDifficultyWeightFactory extends SelectionSorterWeightFactory { + interface NullDifficultyWeightFactory extends SorterWeightFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index 38a265b95f..e6f889b4c1 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -7,6 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -18,7 +19,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -63,7 +63,7 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe protected EntitySorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -156,11 +156,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -247,7 +247,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull EntitySelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index a7b41b3538..9a83d07f72 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -7,6 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -18,7 +19,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -61,7 +61,7 @@ public class ValueSelectorConfig extends SelectorConfig { protected ValueSorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -162,11 +162,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -258,7 +258,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull ValueSelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 2f5ce4666f..56f760a0e5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -6,6 +6,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -22,7 +23,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector; @@ -315,7 +315,7 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SelectionSorterWeightFactory sorterWeightFactory = + SorterWeightFactory sorterWeightFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 7c645dabea..68888e48cc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.function.Function; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -21,7 +22,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; @@ -336,7 +336,7 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SelectionSorterWeightFactory sorterWeightFactory = + SorterWeightFactory sorterWeightFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java index e820011ca0..3dbf62f8e8 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import ai.timefold.solver.core.testdomain.TestdataEntity; @@ -17,7 +18,7 @@ class WeightFactorySelectionSorterTest { @Test void sortAscending() { - SelectionSorterWeightFactory weightFactory = (solution, selection) -> Integer + SorterWeightFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); WeightFactorySelectionSorter selectionSorter = new WeightFactorySelectionSorter<>( weightFactory, SelectionSorterOrder.ASCENDING); @@ -33,7 +34,7 @@ void sortAscending() { @Test void sortDescending() { - SelectionSorterWeightFactory weightFactory = (solution, selection) -> Integer + SorterWeightFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); WeightFactorySelectionSorter selectionSorter = new WeightFactorySelectionSorter<>( weightFactory, SelectionSorterOrder.DESCENDING); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 6335c09f87..3c2d3b5d51 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -7,6 +7,7 @@ import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -14,7 +15,6 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SortingEntitySelector; @@ -208,7 +208,7 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionSorterWeightFactory - implements SelectionSorterWeightFactory { + implements SorterWeightFactory { @Override public Comparable createSorterWeight(TestdataSolution testdataSolution, TestdataEntity selection) { return 0; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 0b3f53690c..d413f6a86a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.stream.Stream; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -21,7 +22,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.ProbabilityValueSelector; @@ -311,7 +311,7 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionSorterWeightFactory - implements SelectionSorterWeightFactory { + implements SorterWeightFactory { @Override public Comparable createSorterWeight(TestdataSolution testdataSolution, TestdataValue selection) { return 0; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java index 7659168c32..fcc8e294fc 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.testdomain.difficultyweight; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; public class TestdataDifficultyWeightFactory implements - SelectionSorterWeightFactory { + SorterWeightFactory { @Override public TestdataDifficultyWeightComparable createSorterWeight(TestdataDifficultyWeightSolution solution, diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 18bcaf17bb..8531214e76 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1600,13 +1600,13 @@ The solver may choose to reuse them in different contexts. [#sortedSelectionBySelectionSorterWeightFactory] -===== Sorted selection by `SelectionSorterWeightFactory` +===== Sorted selection by `SorterWeightFactory` -If you need the entire solution to sort a ``Selector``, use a `SelectionSorterWeightFactory` instead: +If you need the entire solution to sort a ``Selector``, use a `SorterWeightFactory` instead: [source,java,options="nowrap"] ---- -public interface SelectionSorterWeightFactory { +public interface SorterWeightFactory { Comparable createSorterWeight(Solution_ solution, T selection); @@ -1627,7 +1627,7 @@ You'll also need to configure it (unless it's annotated on the domain model and [NOTE] ==== -`SelectionSorterWeightFactory` implementations are expected to be stateless. +`SorterWeightFactory` implementations are expected to be stateless. The solver may choose to reuse them in different contexts. ==== @@ -2223,4 +2223,4 @@ add the `moveIteratorFactoryCustomProperties` element and use xref:using-timefol [WARNING] ==== A custom `MoveIteratorFactory` implementation must ensure that it does not move xref:responding-to-change/responding-to-change.adoc#pinnedPlanningEntities[pinned entities]. -==== \ No newline at end of file +==== From a222d19c78bcc668e74ff193b6324556a4deeeaf Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 24 Sep 2025 10:41:16 -0300 Subject: [PATCH 03/33] chore: update factory class for moves --- core/src/build/revapi-differences.json | 15 +++++++++++++++ .../selector/move/MoveSelectorConfig.java | 10 +++++----- .../move/AbstractMoveSelectorFactory.java | 4 ++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index 729accf328..f91d3e2310 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -378,6 +378,21 @@ "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", "justification": "New weight factory base class" + }, + { + "ignore": true, + "code": "java.method.returnTypeTypeParametersChanged", + "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", + "justification": "New weight factory base class" + }, + { + "ignore": true, + "code": "java.method.parameterTypeParameterChanged", + "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", + "parameterIndex": "0", + "justification": "New weight factory base class" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 6d9f7df4f9..e05a5b42a5 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -7,6 +7,7 @@ import jakarta.xml.bind.annotation.XmlSeeAlso; import jakarta.xml.bind.annotation.XmlType; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -33,7 +34,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -82,7 +82,7 @@ public abstract class MoveSelectorConfig filterClass = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -128,11 +128,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -202,7 +202,7 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { } public @NonNull Config_ withSorterWeightFactoryClass( - @NonNull Class sorterWeightFactoryClass) { + @NonNull Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index ef3f58f0dc..b1676c1556 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -2,6 +2,7 @@ import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; 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.common.decorator.SelectionSorterOrder; @@ -14,7 +15,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.CachingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector; @@ -195,7 +195,7 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterWeightFactoryClass != null) { - SelectionSorterWeightFactory> sorterWeightFactory = + SorterWeightFactory> sorterWeightFactory = ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); From f91745a36dccd5ed22959efab3dedacea2f4e8db Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 25 Sep 2025 07:57:15 -0300 Subject: [PATCH 04/33] feat: enable sorting for CH and list variable --- .../domain/variable/PlanningListVariable.java | 36 ++- ...aultConstructionHeuristicPhaseFactory.java | 9 - .../descriptor/ListVariableDescriptor.java | 2 + .../list/DestinationSelectorFactory.java | 34 +- ...DefaultConstructionHeuristicPhaseTest.java | 303 ++++++++++++++++++ .../ListSortableEntityComparator.java | 11 + .../ListSortableValueComparator.java | 11 + .../OneValuePerEntityEasyScoreCalculator.java | 22 ++ .../compartor/TestdataListSortableEntity.java | 41 +++ .../TestdataListSortableSolution.java | 73 +++++ .../compartor/TestdataListSortableValue.java | 38 +++ .../ListSortableEntityComparator.java | 11 + .../ListSortableValueComparator.java | 11 + ...aluePerEntityRangeEasyScoreCalculator.java | 23 ++ ...dataListSortableEntityProvidingEntity.java | 55 ++++ ...taListSortableEntityProvidingSolution.java | 69 ++++ ...tdataListSortableEntityProvidingValue.java | 38 +++ .../construction-heuristics.adoc | 23 +- 18 files changed, 790 insertions(+), 20 deletions(-) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java index c6d28fc223..5c73946912 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java @@ -6,8 +6,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.util.Comparator; import java.util.List; +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; 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; @@ -55,5 +57,37 @@ String[] valueRangeProviderRefs() default {}; - // TODO value comparison: https://issues.redhat.com/browse/PLANNER-2542 + /** + * Allows a collection of planning values for this variable to be sorted by strength. + * A strengthWeight estimates how strong a planning value is. + * Some algorithms benefit from planning on weaker planning values first or from focusing on them. + *

+ * The {@link Comparator} should sort in ascending strength. + * For example: sorting 3 computers on strength based on their RAM capacity: + * Computer B (1GB RAM), Computer A (2GB RAM), Computer C (7GB RAM), + *

+ * Do not use together with {@link #strengthWeightFactoryClass()}. + * + * @return {@link PlanningVariable.NullStrengthComparator} when it is null (workaround for annotation limitation) + * @see #strengthWeightFactoryClass() + */ + Class strengthComparatorClass() default PlanningVariable.NullStrengthComparator.class; + + /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ + interface NullStrengthComparator extends Comparator { + } + + /** + * The {@link SorterWeightFactory} alternative for {@link #strengthComparatorClass()}. + *

+ * Do not use together with {@link #strengthComparatorClass()}. + * + * @return {@link PlanningVariable.NullStrengthWeightFactory} when it is null (workaround for annotation limitation) + * @see #strengthComparatorClass() + */ + Class strengthWeightFactoryClass() default PlanningVariable.NullStrengthWeightFactory.class; + + /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ + interface NullStrengthWeightFactory extends SorterWeightFactory { + } } 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 f14e513706..c125cb8214 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 @@ -116,8 +116,6 @@ private EntityPlacerConfig buildDefaultEntityPlacerConfig(HeuristicConfigPoli if (listVariableDescriptor == null) { return Optional.empty(); } - 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 @@ -130,13 +128,6 @@ The entity (%s) has both basic and list variables and cannot be deduced automati return Optional.of(listVariableDescriptor); } - private static void failIfConfigured(Object configValue, String configName) { - if (configValue != null) { - throw new IllegalArgumentException("Construction Heuristic phase with a list variable does not support " - + configName + " configuration. Remove the " + configName + " (" + configValue + ") from the config."); - } - } - @SuppressWarnings("rawtypes") public static EntityPlacerConfig buildListVariableQueuedValuePlacerConfig(HeuristicConfigPolicy configPolicy, ListVariableDescriptor variableDescriptor) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java index 19aff357f0..aedb87a7f8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java @@ -60,6 +60,8 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) { PlanningListVariable planningVariableAnnotation = variableMemberAccessor.getAnnotation(PlanningListVariable.class); allowsUnassignedValues = planningVariableAnnotation.allowsUnassignedValues(); processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs()); + processStrength(planningVariableAnnotation.strengthComparatorClass(), + planningVariableAnnotation.strengthWeightFactoryClass()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java index 0589ad1999..dee2ffe252 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java @@ -1,5 +1,8 @@ package ai.timefold.solver.core.impl.heuristic.selector.list; +import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY; +import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.NONE; + import java.util.Objects; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -35,7 +38,36 @@ public DestinationSelector buildDestinationSelector(HeuristicConfigPo public DestinationSelector buildDestinationSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection, String entityValueRangeRecorderId) { var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - var entitySelector = EntitySelectorFactory. create(Objects.requireNonNull(config.getEntitySelectorConfig())) + var entitySelectorConfig = Objects.requireNonNull(config.getEntitySelectorConfig()).copyConfig(); + var hasSortManner = configPolicy.getEntitySorterManner() != null + && configPolicy.getEntitySorterManner() != NONE; + var entityDescriptor = deduceEntityDescriptor(configPolicy, entitySelectorConfig.getEntityClass()); + var hasDifficultySorter = entityDescriptor.getDecreasingDifficultySorter() != null; + var isEntityRangeSortingValid = + // no entity value range, so we accept any sorting manner + entityValueRangeRecorderId == null + // the entity value range is specified + // we only accept DECREASING_DIFFICULTY, + // indicating that the configuration requires sorting + || hasSortManner && configPolicy.getEntitySorterManner() == DECREASING_DIFFICULTY; + + if (hasSortManner && hasDifficultySorter && isEntityRangeSortingValid + && entitySelectorConfig.getSorterManner() == null) { + entitySelectorConfig.setCacheType(SelectionCacheType.PHASE); + entitySelectorConfig.setSelectionOrder(SelectionOrder.SORTED); + entitySelectorConfig.setSorterManner(configPolicy.getEntitySorterManner()); + } + if (entityValueRangeRecorderId != null && entitySelectorConfig.getSelectionOrder() != null + && entitySelectorConfig.getSelectionOrder() == SelectionOrder.SORTED) { + // Sorting entities is not permitted when using an entity value range, + // as the list of reachable entities is only generated after selecting a value. + // This process prevents sorting and caching at the phase level. + throw new IllegalStateException(""" + The destination selector cannot to sort the entity list when an entity value range is used. + Maybe remove the setting "EntitySorterManner" from the phase config. + Maybe remove the entity selector sorting settings from the destination config."""); + } + var entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder, new ValueRangeRecorderId(entityValueRangeRecorderId, false)); var valueSelector = buildIterableValueSelector(configPolicy, entitySelector.getEntityDescriptor(), 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 7b8e81d577..65ca4f0b38 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 @@ -13,9 +13,30 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; +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.list.DestinationSelectorConfig; +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.heuristic.selector.value.ValueSorterManner; +import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig; +import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; +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; 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.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.sort.compartor.OneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableEntity; +import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableSolution; +import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableValue; 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; @@ -24,6 +45,10 @@ import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingSolution; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingValue; +import ai.timefold.solver.core.testdomain.list.valuerange.compartor.OneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingValue; 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; @@ -43,6 +68,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; @Execution(ExecutionMode.CONCURRENT) class DefaultConstructionHeuristicPhaseTest { @@ -319,6 +346,278 @@ void constructionHeuristicAllocateToValueFromQueue() { .filter(e -> e.getValue() == null)).isEmpty(); } + private static List generateConstructionHeuristicTestValues() { + var values = new ArrayList(); + // Simple configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE) + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + // Advanced configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + // Hack to prevent the default sorting option, + // which is DECREASING_DIFFICULTY_IF_AVAILABLE + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.NONE))))), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + // Hack to prevent the default sorting option, + // which is DECREASING_DIFFICULTY_IF_AVAILABLE + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.NONE))))), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + return values; + } + + @ParameterizedTest + @MethodSource("generateConstructionHeuristicTestValues") + void constructionHeuristicAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class, + TestdataListSortableValue.class) + .withEasyScoreCalculatorClass(OneValuePerEntityEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataListSortableSolution.generateSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + for (var i = 0; i < 3; i++) { + // The calculator will give a better score for an entity with only one value. + assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) + .isEqualTo(phaseConfig.expected[i]); + } + } + + private static List generateEntityRangeConstructionHeuristicTestValues() { + var values = new ArrayList(); + // Simple configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + // Since we are starting from decreasing strength + // and the entities cannot be sorted, + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.NONE), + // The order is not guaranteed + null)); + // Advanced configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig()))), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig()))), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + return values; + } + + @ParameterizedTest + @MethodSource("generateEntityRangeConstructionHeuristicTestValues") + void constructionHeuristicEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, + TestdataListSortableEntityProvidingEntity.class, + TestdataListSortableEntityProvidingValue.class) + .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + // The calculator will give a better score for an entity with only one value. + assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) + .isEqualTo(phaseConfig.expected[i]); + } + } + } + + @Test + void failConstructionHeuristicEntityRangeAllocateToValueFromQueue() { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, + TestdataListSortableEntityProvidingEntity.class, + TestdataListSortableEntityProvidingValue.class) + .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases(new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + // Sorting entities is not permitted when using an entity value range + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH)); + + var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The destination selector cannot to sort the entity list when an entity value range is used."); + } + @Test void failMixedModelDefaultConfiguration() { var solverConfig = PlannerTestUtils @@ -329,4 +628,8 @@ void failMixedModelDefaultConfiguration() { .hasMessageContaining( "has both basic and list variables and cannot be deduced automatically"); } + + private record ConstructionHeuristicTestConfig(ConstructionHeuristicPhaseConfig config, int[] expected) { + + } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java new file mode 100644 index 0000000000..a55d0bfdcd --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +import java.util.Comparator; + +public class ListSortableEntityComparator implements Comparator { + + @Override + public int compare(TestdataListSortableEntity e1, TestdataListSortableEntity e2) { + return e1.getDifficulty() - e2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java new file mode 100644 index 0000000000..be59aeb5a7 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +import java.util.Comparator; + +public class ListSortableValueComparator implements Comparator { + + @Override + public int compare(TestdataListSortableValue v1, TestdataListSortableValue v2) { + return v1.getStrength() - v2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java new file mode 100644 index 0000000000..8ca2ab759d --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java @@ -0,0 +1,22 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +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 OneValuePerEntityEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull SimpleScore calculateScore(@NonNull TestdataListSortableSolution testdataListSortableSolution) { + var score = 0; + for (var entity : testdataListSortableSolution.getEntityList()) { + if (entity.getValueList().size() == 1) { + score += 10; + } + score++; + } + return SimpleScore.of(score); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java new file mode 100644 index 0000000000..53f293e23a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java @@ -0,0 +1,41 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +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 = ListSortableEntityComparator.class) +public class TestdataListSortableEntity extends TestdataObject { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = ListSortableValueComparator.class) + private List valueList; + private int difficulty; + + public TestdataListSortableEntity() { + } + + public TestdataListSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + this.valueList = new ArrayList<>(); + } + + 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/list/sort/compartor/TestdataListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java new file mode 100644 index 0000000000..09346cea9d --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java @@ -0,0 +1,73 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +import java.util.List; +import java.util.stream.IntStream; + +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 TestdataListSortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataListSortableSolution.class, + TestdataListSortableEntity.class, + TestdataListSortableValue.class); + } + + public static TestdataListSortableSolution generateSolution(int valueCount, int entityCount) { + var entityList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataListSortableEntity("Generated Entity " + i, i)) + .toList(); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataListSortableValue("Generated Value " + i, i)) + .toList(); + TestdataListSortableSolution solution = new TestdataListSortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private SimpleScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } + + public void removeEntity(TestdataListSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java new file mode 100644 index 0000000000..3faa35d16f --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java @@ -0,0 +1,38 @@ +package ai.timefold.solver.core.testdomain.list.sort.compartor; + +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(difficultyComparatorClass = ListSortableValueComparator.class) +public class TestdataListSortableValue extends TestdataObject { + + private int strength; + + @InverseRelationShadowVariable(sourceVariableName = "valueList") + private TestdataListSortableEntity entity; + + public TestdataListSortableValue() { + } + + public TestdataListSortableValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + public TestdataListSortableEntity getEntity() { + return entity; + } + + public void setEntity(TestdataListSortableEntity entity) { + this.entity = entity; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java new file mode 100644 index 0000000000..92cdfaf86d --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +import java.util.Comparator; + +public class ListSortableEntityComparator implements Comparator { + + @Override + public int compare(TestdataListSortableEntityProvidingEntity e1, TestdataListSortableEntityProvidingEntity e2) { + return e1.getDifficulty() - e2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java new file mode 100644 index 0000000000..763772c210 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +import java.util.Comparator; + +public class ListSortableValueComparator implements Comparator { + + @Override + public int compare(TestdataListSortableEntityProvidingValue v1, TestdataListSortableEntityProvidingValue v2) { + return v1.getStrength() - v2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java new file mode 100644 index 0000000000..531121491c --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java @@ -0,0 +1,23 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +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 OneValuePerEntityRangeEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull SimpleScore + calculateScore(@NonNull TestdataListSortableEntityProvidingSolution testdataListSortableEntityProvidingSolution) { + var score = 0; + for (var entity : testdataListSortableEntityProvidingSolution.getEntityList()) { + if (entity.getValueList().size() == 1) { + score += 10; + } + score++; + } + return SimpleScore.of(score); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..a42e4d102e --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java @@ -0,0 +1,55 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyComparatorClass = ListSortableEntityComparator.class) +public class TestdataListSortableEntityProvidingEntity extends TestdataObject { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = ListSortableValueComparator.class) + private List valueList; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataListSortableEntityProvidingEntity() { + } + + public TestdataListSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + this.valueList = new ArrayList<>(); + } + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + 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/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java new file mode 100644 index 0000000000..4f60d54177 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java @@ -0,0 +1,69 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataListSortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataListSortableEntityProvidingSolution.class, + TestdataListSortableEntityProvidingEntity.class, + TestdataListSortableEntityProvidingValue.class); + } + + public static TestdataListSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount) { + var entityList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataListSortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList(); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataListSortableEntityProvidingValue("Generated Value " + i, i)) + .toList(); + var solution = new TestdataListSortableEntityProvidingSolution(); + var random = new Random(0); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + Collections.shuffle(valueRange, random); + entity.setValueRange(valueRange); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private SimpleScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public SimpleScore getScore() { + return score; + } + + public void setScore(SimpleScore score) { + this.score = score; + } + + public void removeEntity(TestdataListSortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java new file mode 100644 index 0000000000..1b9513d3d8 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java @@ -0,0 +1,38 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.compartor; + +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(difficultyComparatorClass = ListSortableValueComparator.class) +public class TestdataListSortableEntityProvidingValue extends TestdataObject { + + private int strength; + + @InverseRelationShadowVariable(sourceVariableName = "valueList") + private TestdataListSortableEntityProvidingEntity entity; + + public TestdataListSortableEntityProvidingValue() { + } + + public TestdataListSortableEntityProvidingValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + public TestdataListSortableEntityProvidingEntity getEntity() { + return entity; + } + + public void setEntity(TestdataListSortableEntityProvidingEntity entity) { + this.entity = entity; + } +} diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc index 79dbda1baf..cea76665d0 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc @@ -545,14 +545,19 @@ Advanced configuration for a single entity class with a single variable: SORTED INCREASING_STRENGTH - - - PHASE - SORTED - DECREASING_DIFFICULTY - - - + + + + + + PHASE + SORTED + DECREASING_DIFFICULTY + + + + + ---- @@ -887,4 +892,4 @@ It is supported to only partition the Construction Heuristic phase. Other `Selector` customizations can also reduce the number of moves generated by step: * xref:optimization-algorithms/overview.adoc#filteredSelection[Filtered selection] -* xref:optimization-algorithms/overview.adoc#limitedSelection[Limited selection] \ No newline at end of file +* xref:optimization-algorithms/overview.adoc#limitedSelection[Limited selection] From e2345dd01e2df2135403fa0eecb581095017f8f1 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 25 Sep 2025 09:09:22 -0300 Subject: [PATCH 05/33] chore: improve tests --- ...DefaultConstructionHeuristicPhaseTest.java | 174 +++++++++++++++--- .../OneValuePerEntityEasyScoreCalculator.java | 17 +- .../TestdataListSortableSolution.java | 8 +- ...aluePerEntityRangeEasyScoreCalculator.java | 17 +- ...taListSortableEntityProvidingSolution.java | 8 +- 5 files changed, 175 insertions(+), 49 deletions(-) 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 65ca4f0b38..b9f7d64884 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 @@ -13,6 +13,8 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; +import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicForagerConfig; +import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicPickEarlyType; 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; @@ -348,12 +350,57 @@ void constructionHeuristicAllocateToValueFromQueue() { private static List generateConstructionHeuristicTestValues() { var values = new ArrayList(); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT_DECREASING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // the entities are being read in decreasing order of difficulty, + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // the values are being read in increase order of strength, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT_DECREASING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // the entities are being read in decreasing order of difficulty, + // and the values are being read in increase order of strength + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // and the values are being read in decreasing order of strength + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 })); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT_DECREASING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // the entities are being read in decreasing order of difficulty, + // and the values are being read in decreasing order of strength + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 })); + // Allocate from pool // Simple configuration values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // Since we are starting from decreasing strength // and the entities are being read in decreasing order of difficulty, // this is expected: e1[1], e2[2], and e3[3] @@ -362,35 +409,45 @@ private static List generateConstructionHeurist new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE), + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE), + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); // Advanced configuration @@ -410,7 +467,9 @@ private static List generateConstructionHeurist .withEntitySelectorConfig(new EntitySelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // Since we are starting from decreasing strength // and the entities are being read in decreasing order of difficulty, // this is expected: e1[1], e2[2], and e3[3] @@ -433,7 +492,9 @@ private static List generateConstructionHeurist // which is DECREASING_DIFFICULTY_IF_AVAILABLE .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.NONE))))), + .withSorterManner(EntitySorterManner.NONE))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( @@ -452,7 +513,9 @@ private static List generateConstructionHeurist .withEntitySelectorConfig(new EntitySelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( @@ -473,7 +536,9 @@ private static List generateConstructionHeurist // which is DECREASING_DIFFICULTY_IF_AVAILABLE .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.NONE))))), + .withSorterManner(EntitySorterManner.NONE))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); return values; @@ -481,7 +546,7 @@ private static List generateConstructionHeurist @ParameterizedTest @MethodSource("generateConstructionHeuristicTestValues") - void constructionHeuristicAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + void constructionHeuristicListVariableAllocateValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class, @@ -508,7 +573,9 @@ private static List generateEntityRangeConstruc new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // Since we are starting from decreasing strength // and the entities cannot be sorted, // this is expected: e1[3], e2[2], and e3[1] @@ -517,28 +584,36 @@ private static List generateEntityRangeConstruc new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE), + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH), + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE), + .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.NONE), + .withValueSorterManner(ValueSorterManner.NONE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // The order is not guaranteed null)); // Advanced configuration @@ -553,7 +628,9 @@ private static List generateEntityRangeConstruc .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() .withValueSelectorConfig( new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) - .withDestinationSelectorConfig(new DestinationSelectorConfig()))), + .withDestinationSelectorConfig(new DestinationSelectorConfig()))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] new int[] { 2, 1, 0 })); values.add(new ConstructionHeuristicTestConfig( @@ -567,7 +644,9 @@ private static List generateEntityRangeConstruc .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() .withValueSelectorConfig( new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) - .withDestinationSelectorConfig(new DestinationSelectorConfig()))), + .withDestinationSelectorConfig(new DestinationSelectorConfig()))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] new int[] { 0, 1, 2 })); return values; @@ -575,7 +654,7 @@ private static List generateEntityRangeConstruc @ParameterizedTest @MethodSource("generateEntityRangeConstructionHeuristicTestValues") - void constructionHeuristicEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + void constructionHeuristicListVariableEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, @@ -598,20 +677,61 @@ void constructionHeuristicEntityRangeAllocateToValueFromQueue(ConstructionHeuris } } - @Test - void failConstructionHeuristicEntityRangeAllocateToValueFromQueue() { + private static List generateFailingConstructionHeuristicTestValues() { + // Entity sorting will throw errors + var values = new ArrayList(); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT_DECREASING), + null)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT_DECREASING), + null)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT_DECREASING), + null)); + // Allocate from pool + // Simple configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) + .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), + null)); + // Advanced configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), + null)); + return values; + } + + @ParameterizedTest + @MethodSource("generateFailingConstructionHeuristicTestValues") + void failConstructionHeuristicEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, TestdataListSortableEntityProvidingEntity.class, TestdataListSortableEntityProvidingValue.class) .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - // Sorting entities is not permitted when using an entity value range - .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH)); - + .withPhases(phaseConfig.config()); var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3); assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) .hasMessageContaining( diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java index 8ca2ab759d..b158e3e0a6 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java @@ -1,22 +1,25 @@ package ai.timefold.solver.core.testdomain.list.sort.compartor; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import org.jspecify.annotations.NonNull; public class OneValuePerEntityEasyScoreCalculator - implements EasyScoreCalculator { + implements EasyScoreCalculator { @Override - public @NonNull SimpleScore calculateScore(@NonNull TestdataListSortableSolution testdataListSortableSolution) { - var score = 0; + public @NonNull HardSoftScore calculateScore(@NonNull TestdataListSortableSolution testdataListSortableSolution) { + var softScore = 0; + var hardScore = 0; for (var entity : testdataListSortableSolution.getEntityList()) { if (entity.getValueList().size() == 1) { - score += 10; + softScore -= 10; + } else { + hardScore -= 10; } - score++; + hardScore--; } - return SimpleScore.of(score); + return HardSoftScore.of(hardScore, softScore); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java index 09346cea9d..b51a257bac 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java @@ -7,7 +7,7 @@ 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.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @PlanningSolution @@ -35,7 +35,7 @@ public static TestdataListSortableSolution generateSolution(int valueCount, int private List valueList; private List entityList; - private SimpleScore score; + private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty @@ -57,11 +57,11 @@ public void setEntityList(List entityList) { } @PlanningScore - public SimpleScore getScore() { + public HardSoftScore getScore() { return score; } - public void setScore(SimpleScore score) { + public void setScore(HardSoftScore score) { this.score = score; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java index 531121491c..425588376e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java @@ -1,23 +1,26 @@ package ai.timefold.solver.core.testdomain.list.valuerange.compartor; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import org.jspecify.annotations.NonNull; public class OneValuePerEntityRangeEasyScoreCalculator - implements EasyScoreCalculator { + implements EasyScoreCalculator { @Override - public @NonNull SimpleScore + public @NonNull HardSoftScore calculateScore(@NonNull TestdataListSortableEntityProvidingSolution testdataListSortableEntityProvidingSolution) { - var score = 0; + var softScore = 0; + var hardScore = 0; for (var entity : testdataListSortableEntityProvidingSolution.getEntityList()) { if (entity.getValueList().size() == 1) { - score += 10; + softScore -= 10; + } else { + hardScore -= 10; } - score++; + hardScore--; } - return SimpleScore.of(score); + return HardSoftScore.of(hardScore, softScore); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java index 4f60d54177..5b91c7c14a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java @@ -9,7 +9,7 @@ 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.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; @PlanningSolution @@ -41,7 +41,7 @@ public static TestdataListSortableEntityProvidingSolution generateSolution(int v } private List entityList; - private SimpleScore score; + private HardSoftScore score; @PlanningEntityCollectionProperty public List getEntityList() { @@ -53,11 +53,11 @@ public void setEntityList(List entity } @PlanningScore - public SimpleScore getScore() { + public HardSoftScore getScore() { return score; } - public void setScore(SimpleScore score) { + public void setScore(HardSoftScore score) { this.score = score; } From 64eead18c89b0be5185655d88691ccb93c9a7593 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 25 Sep 2025 14:29:13 -0300 Subject: [PATCH 06/33] chore: add new tests --- ...DefaultConstructionHeuristicPhaseTest.java | 76 ++++++++++++++++--- .../ListSortableEntityComparator.java | 2 +- .../ListSortableValueComparator.java | 2 +- .../OneValuePerEntityEasyScoreCalculator.java | 2 +- .../TestdataListSortableEntity.java | 2 +- .../TestdataListSortableSolution.java | 2 +- .../TestdataListSortableValue.java | 4 +- .../factory/ListSortableEntityFactory.java | 13 ++++ .../factory/ListSortableValueFactory.java | 13 ++++ ...uePerEntityFactoryEasyScoreCalculator.java | 25 ++++++ .../TestdataListFactorySortableEntity.java | 46 +++++++++++ .../TestdataListFactorySortableSolution.java | 73 ++++++++++++++++++ .../TestdataListFactorySortableValue.java | 43 +++++++++++ .../ListSortableEntityComparator.java | 2 +- .../ListSortableValueComparator.java | 2 +- ...aluePerEntityRangeEasyScoreCalculator.java | 2 +- ...dataListSortableEntityProvidingEntity.java | 2 +- ...taListSortableEntityProvidingSolution.java | 2 +- ...tdataListSortableEntityProvidingValue.java | 4 +- .../factory/ListSortableEntityFactory.java | 15 ++++ .../factory/ListSortableValueFactory.java | 15 ++++ ...EntityRangeFactoryEasyScoreCalculator.java | 26 +++++++ ...tFactorySortableEntityProvidingEntity.java | 61 +++++++++++++++ ...actorySortableEntityProvidingSolution.java | 69 +++++++++++++++++ ...stFactorySortableEntityProvidingValue.java | 44 +++++++++++ 25 files changed, 523 insertions(+), 24 deletions(-) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/ListSortableEntityComparator.java (81%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/ListSortableValueComparator.java (81%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/OneValuePerEntityEasyScoreCalculator.java (92%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/TestdataListSortableEntity.java (94%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/TestdataListSortableSolution.java (97%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/{compartor => comparator}/TestdataListSortableValue.java (86%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/ListSortableEntityComparator.java (81%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/ListSortableValueComparator.java (81%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/OneValuePerEntityRangeEasyScoreCalculator.java (92%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/TestdataListSortableEntityProvidingEntity.java (96%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/TestdataListSortableEntityProvidingSolution.java (97%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/{compartor => sort/comparator}/TestdataListSortableEntityProvidingValue.java (87%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java 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 b9f7d64884..fe0ed2ab64 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 @@ -35,10 +35,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.sort.compartor.OneValuePerEntityEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableEntity; -import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableSolution; -import ai.timefold.solver.core.testdomain.list.sort.compartor.TestdataListSortableValue; +import ai.timefold.solver.core.testdomain.list.sort.comparator.OneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableEntity; +import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableSolution; +import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableValue; +import ai.timefold.solver.core.testdomain.list.sort.factory.OneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableEntity; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableValue; 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; @@ -47,10 +51,14 @@ import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingSolution; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingValue; -import ai.timefold.solver.core.testdomain.list.valuerange.compartor.OneValuePerEntityRangeEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.list.valuerange.compartor.TestdataListSortableEntityProvidingValue; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.OneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingValue; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.OneValuePerEntityRangeFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingValue; 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; @@ -546,7 +554,7 @@ private static List generateConstructionHeurist @ParameterizedTest @MethodSource("generateConstructionHeuristicTestValues") - void constructionHeuristicListVariableAllocateValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class, @@ -566,6 +574,28 @@ void constructionHeuristicListVariableAllocateValueFromQueue(ConstructionHeurist } } + @ParameterizedTest + @MethodSource("generateConstructionHeuristicTestValues") + void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListFactorySortableSolution.class, TestdataListFactorySortableEntity.class, + TestdataListFactorySortableValue.class) + .withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataListFactorySortableSolution.generateSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + for (var i = 0; i < 3; i++) { + // The calculator will give a better score for an entity with only one value. + assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) + .isEqualTo(phaseConfig.expected[i]); + } + } + private static List generateEntityRangeConstructionHeuristicTestValues() { var values = new ArrayList(); // Simple configuration @@ -654,7 +684,8 @@ private static List generateEntityRangeConstruc @ParameterizedTest @MethodSource("generateEntityRangeConstructionHeuristicTestValues") - void constructionHeuristicListVariableEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( + ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, @@ -677,6 +708,31 @@ void constructionHeuristicListVariableEntityRangeAllocateToValueFromQueue(Constr } } + @ParameterizedTest + @MethodSource("generateEntityRangeConstructionHeuristicTestValues") + void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListFactorySortableEntityProvidingSolution.class, + TestdataListFactorySortableEntityProvidingEntity.class, + TestdataListFactorySortableEntityProvidingValue.class) + .withEasyScoreCalculatorClass(OneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataListFactorySortableEntityProvidingSolution.generateSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + // The calculator will give a better score for an entity with only one value. + assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); + assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) + .isEqualTo(phaseConfig.expected[i]); + } + } + } + private static List generateFailingConstructionHeuristicTestValues() { // Entity sorting will throw errors var values = new ArrayList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java similarity index 81% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java index a55d0bfdcd..8bb8dc635e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableEntityComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; import java.util.Comparator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java similarity index 81% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java index be59aeb5a7..f636a0a571 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/ListSortableValueComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; import java.util.Comparator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java similarity index 92% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java index b158e3e0a6..c720265857 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/OneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java similarity index 94% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java index 53f293e23a..7e6c3ad06b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; import java.util.ArrayList; import java.util.List; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java similarity index 97% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java index b51a257bac..b2a3fa7959 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; import java.util.List; import java.util.stream.IntStream; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java similarity index 86% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java index 3faa35d16f..1a7a52280b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/compartor/TestdataListSortableValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java @@ -1,10 +1,10 @@ -package ai.timefold.solver.core.testdomain.list.sort.compartor; +package ai.timefold.solver.core.testdomain.list.sort.comparator; 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(difficultyComparatorClass = ListSortableValueComparator.class) +@PlanningEntity public class TestdataListSortableValue extends TestdataObject { private int strength; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java new file mode 100644 index 0000000000..084e236521 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; + +public class ListSortableEntityFactory + implements SorterWeightFactory { + + @Override + public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, + TestdataListFactorySortableEntity selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java new file mode 100644 index 0000000000..f29746a094 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; + +public class ListSortableValueFactory + implements SorterWeightFactory { + + @Override + public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, + TestdataListFactorySortableValue selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..be4245f982 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java @@ -0,0 +1,25 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore calculateScore(@NonNull TestdataListFactorySortableSolution testdataListFactorySortableSolution) { + var softScore = 0; + var hardScore = 0; + for (var entity : testdataListFactorySortableSolution.getEntityList()) { + if (entity.getValueList().size() == 1) { + softScore -= 10; + } else { + hardScore -= 10; + } + hardScore--; + } + return HardSoftScore.of(hardScore, softScore); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java new file mode 100644 index 0000000000..7494590369 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java @@ -0,0 +1,46 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +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(difficultyWeightFactoryClass = ListSortableEntityFactory.class) +public class TestdataListFactorySortableEntity extends TestdataObject implements Comparable { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = ListSortableValueFactory.class) + private List valueList; + private int difficulty; + + public TestdataListFactorySortableEntity() { + } + + public TestdataListFactorySortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + this.valueList = new ArrayList<>(); + } + + 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; + } + + @Override + public int compareTo(TestdataListFactorySortableEntity o) { + return difficulty - o.difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java new file mode 100644 index 0000000000..a21fc04245 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java @@ -0,0 +1,73 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +import java.util.List; +import java.util.stream.IntStream; + +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.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataListFactorySortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataListFactorySortableSolution.class, + TestdataListFactorySortableEntity.class, + TestdataListFactorySortableValue.class); + } + + public static TestdataListFactorySortableSolution generateSolution(int valueCount, int entityCount) { + var entityList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataListFactorySortableEntity("Generated Entity " + i, i)) + .toList(); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataListFactorySortableValue("Generated Value " + i, i)) + .toList(); + TestdataListFactorySortableSolution solution = new TestdataListFactorySortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataListFactorySortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java new file mode 100644 index 0000000000..8ddfbdbd5a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java @@ -0,0 +1,43 @@ +package ai.timefold.solver.core.testdomain.list.sort.factory; + +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 TestdataListFactorySortableValue extends TestdataObject implements Comparable { + + private int strength; + + @InverseRelationShadowVariable(sourceVariableName = "valueList") + private TestdataListFactorySortableEntity entity; + + public TestdataListFactorySortableValue() { + } + + public TestdataListFactorySortableValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + public TestdataListFactorySortableEntity getEntity() { + return entity; + } + + public void setEntity(TestdataListFactorySortableEntity entity) { + this.entity = entity; + } + + @Override + public int compareTo(TestdataListFactorySortableValue o) { + return strength - o.strength; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java similarity index 81% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java index 92cdfaf86d..5af8f813af 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableEntityComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; import java.util.Comparator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java similarity index 81% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java index 763772c210..85bcc73482 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/ListSortableValueComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; import java.util.Comparator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java similarity index 92% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java index 425588376e..76c238fc92 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/OneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java similarity index 96% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java index a42e4d102e..f04a5bd464 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; import java.util.ArrayList; import java.util.List; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java similarity index 97% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java index 5b91c7c14a..9ae9f9b076 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; import java.util.ArrayList; import java.util.Collections; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java similarity index 87% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java index 1b9513d3d8..cd8f89889e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/compartor/TestdataListSortableEntityProvidingValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java @@ -1,10 +1,10 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.compartor; +package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; 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(difficultyComparatorClass = ListSortableValueComparator.class) +@PlanningEntity public class TestdataListSortableEntityProvidingValue extends TestdataObject { private int strength; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java new file mode 100644 index 0000000000..4c5dd23494 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; + +public class ListSortableEntityFactory + implements + SorterWeightFactory { + + @Override + public Comparable createSorterWeight( + TestdataListFactorySortableEntityProvidingSolution solution, + TestdataListFactorySortableEntityProvidingEntity selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java new file mode 100644 index 0000000000..2ce935f1fc --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; + +public class ListSortableValueFactory + implements + SorterWeightFactory { + + @Override + public Comparable createSorterWeight( + TestdataListFactorySortableEntityProvidingSolution solution, + TestdataListFactorySortableEntityProvidingValue selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..3c2038d0a4 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -0,0 +1,26 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityRangeFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataListFactorySortableEntityProvidingSolution testdataListFactorySortableEntityProvidingSolution) { + var softScore = 0; + var hardScore = 0; + for (var entity : testdataListFactorySortableEntityProvidingSolution.getEntityList()) { + if (entity.getValueList().size() == 1) { + softScore -= 10; + } else { + hardScore -= 10; + } + hardScore--; + } + return HardSoftScore.of(hardScore, softScore); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java new file mode 100644 index 0000000000..eabab053bb --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java @@ -0,0 +1,61 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +import java.util.ArrayList; +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyWeightFactoryClass = ListSortableEntityFactory.class) +public class TestdataListFactorySortableEntityProvidingEntity extends TestdataObject + implements Comparable { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = ListSortableValueFactory.class) + private List valueList; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataListFactorySortableEntityProvidingEntity() { + } + + public TestdataListFactorySortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + this.valueList = new ArrayList<>(); + } + + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + public int getDifficulty() { + return difficulty; + } + + public void setDifficulty(int difficulty) { + this.difficulty = difficulty; + } + + @Override + public int compareTo(TestdataListFactorySortableEntityProvidingEntity o) { + return difficulty - o.difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java new file mode 100644 index 0000000000..1ed9d7f271 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java @@ -0,0 +1,69 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataListFactorySortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataListFactorySortableEntityProvidingSolution.class, + TestdataListFactorySortableEntityProvidingEntity.class, + TestdataListFactorySortableEntityProvidingValue.class); + } + + public static TestdataListFactorySortableEntityProvidingSolution generateSolution(int valueCount, int entityCount) { + var entityList = IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataListFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList(); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataListFactorySortableEntityProvidingValue("Generated Value " + i, i)) + .toList(); + var solution = new TestdataListFactorySortableEntityProvidingSolution(); + var random = new Random(0); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + Collections.shuffle(valueRange, random); + entity.setValueRange(valueRange); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private HardSoftScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataListFactorySortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java new file mode 100644 index 0000000000..2bf1907ef2 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java @@ -0,0 +1,44 @@ +package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; + +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 TestdataListFactorySortableEntityProvidingValue extends TestdataObject + implements Comparable { + + private int strength; + + @InverseRelationShadowVariable(sourceVariableName = "valueList") + private TestdataListFactorySortableEntityProvidingEntity entity; + + public TestdataListFactorySortableEntityProvidingValue() { + } + + public TestdataListFactorySortableEntityProvidingValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + public TestdataListFactorySortableEntityProvidingEntity getEntity() { + return entity; + } + + public void setEntity(TestdataListFactorySortableEntityProvidingEntity entity) { + this.entity = entity; + } + + @Override + public int compareTo(TestdataListFactorySortableEntityProvidingValue o) { + return strength - o.strength; + } +} From 18e520714ba163aed61b30645533234f73e30de0 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 25 Sep 2025 15:11:05 -0300 Subject: [PATCH 07/33] chore: formatting --- .../factory/OneValuePerEntityFactoryEasyScoreCalculator.java | 3 ++- .../OneValuePerEntityRangeFactoryEasyScoreCalculator.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java index be4245f982..8ff8b97180 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java @@ -9,7 +9,8 @@ public class OneValuePerEntityFactoryEasyScoreCalculator implements EasyScoreCalculator { @Override - public @NonNull HardSoftScore calculateScore(@NonNull TestdataListFactorySortableSolution testdataListFactorySortableSolution) { + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataListFactorySortableSolution testdataListFactorySortableSolution) { var softScore = 0; var hardScore = 0; for (var entity : testdataListFactorySortableSolution.getEntityList()) { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java index 3c2038d0a4..797baf833f 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -10,7 +10,8 @@ public class OneValuePerEntityRangeFactoryEasyScoreCalculator @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataListFactorySortableEntityProvidingSolution testdataListFactorySortableEntityProvidingSolution) { + calculateScore( + @NonNull TestdataListFactorySortableEntityProvidingSolution testdataListFactorySortableEntityProvidingSolution) { var softScore = 0; var hardScore = 0; for (var entity : testdataListFactorySortableEntityProvidingSolution.getEntityList()) { From 051bcc8d7d78e2a79f0c3263c39b43ee757d14a3 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 26 Sep 2025 15:54:36 -0300 Subject: [PATCH 08/33] feat: sorting with entity-range --- .../domain/common/SorterWeightFactory.java | 3 + .../selector/AbstractSelectorFactory.java | 40 ++- .../SelectionSorterWeightFactory.java | 2 + .../entity/EntitySelectorFactory.java | 3 +- .../decorator/SortingEntitySelector.java | 43 +++ .../list/DestinationSelectorFactory.java | 30 +- .../move/AbstractMoveSelectorFactory.java | 2 +- .../selector/value/ValueSelectorFactory.java | 2 +- ...DefaultConstructionHeuristicPhaseTest.java | 337 ++++++++---------- .../TestdataListSortableSolution.java | 18 +- .../TestdataListFactorySortableSolution.java | 18 +- ...taListSortableEntityProvidingSolution.java | 16 +- ...actorySortableEntityProvidingSolution.java | 14 +- 13 files changed, 279 insertions(+), 249 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java index 9a669f5c3c..02c9b0d82a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java @@ -5,6 +5,8 @@ import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import org.jspecify.annotations.NullMarked; + /** * Creates a weight to decide the order of a collections of selections * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). @@ -18,6 +20,7 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ +@NullMarked @FunctionalInterface public interface SorterWeightFactory { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/AbstractSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/AbstractSelectorFactory.java index 91a4923f3a..23681be212 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/AbstractSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/AbstractSelectorFactory.java @@ -13,27 +13,39 @@ protected AbstractSelectorFactory(SelectorConfig_ selectorConfig) { } protected void validateCacheTypeVersusSelectionOrder(SelectionCacheType resolvedCacheType, - SelectionOrder resolvedSelectionOrder) { + SelectionOrder resolvedSelectionOrder, boolean hasEntityRange) { switch (resolvedSelectionOrder) { case INHERIT: - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") which should have been resolved by now."); - case ORIGINAL: - case RANDOM: + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has a resolvedSelectionOrder (%s) which should have been resolved by now." + .formatted(config, resolvedSelectionOrder)); + case ORIGINAL, RANDOM: break; - case SORTED: - case SHUFFLED: - case PROBABILISTIC: + case SORTED: { if (resolvedCacheType.isNotCached()) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") which does not support the resolvedCacheType (" + resolvedCacheType + ")."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has a resolvedSelectionOrder (%s) which does not support the resolvedCacheType (%s)." + .formatted(config, resolvedSelectionOrder, resolvedCacheType)); + } + if (hasEntityRange && resolvedCacheType != SelectionCacheType.STEP) { + throw new IllegalArgumentException( + """ + The moveSelectorConfig (%s) has a resolvedSelectionOrder (%s) which does not support the resolvedCacheType (%s). + Maybe set the "cacheType" to STEP.""" + .formatted(config, resolvedSelectionOrder, resolvedCacheType)); + } + break; + } + case SHUFFLED, PROBABILISTIC: + if (resolvedCacheType.isNotCached()) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has a resolvedSelectionOrder (%s) which does not support the resolvedCacheType (%s)." + .formatted(config, resolvedSelectionOrder, resolvedCacheType)); } break; default: - throw new IllegalStateException("The resolvedSelectionOrder (" + resolvedSelectionOrder - + ") is not implemented."); + throw new IllegalStateException( + "The resolvedSelectionOrder (%s) is not implemented.".formatted(resolvedSelectionOrder)); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 98622f2589..6077981eb6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -16,6 +16,8 @@ * Implementations are expected to be stateless. * The solver may choose to reuse instances. * + * @deprecated Deprecated in favor of {@link SorterWeightFactory}. + * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 56f760a0e5..f29ac2b36d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -99,7 +99,8 @@ public EntitySelector buildEntitySelector(HeuristicConfigPolicy extends AbstractCachingEntitySelector { private final SelectionSorter sorter; + private SolverScope solverScope; public SortingEntitySelector(EntitySelector childEntitySelector, SelectionCacheType cacheType, SelectionSorter sorter) { @@ -23,8 +25,40 @@ public SortingEntitySelector(EntitySelector childEntitySelector, Sele // Worker methods // ************************************************************************ + /** + * The method ensures that cached items are loaded and sorted when the cache is set to STEP. + * This logic is necessary + * for making the node compatible with sorting elements at the STEP level when using entity-range. + * For this specific use case, + * we will fetch and sort the data after the phase has started but before the step begins. + */ + private void ensureStepCacheIsLoaded() { + if (cacheType != SelectionCacheType.STEP || cachedEntityList != null) { + return; + } + // At this stage, + // we attempt to load the entity list + // since the iterator may have been requested prior to the start of the step. + constructCache(solverScope); + } + + @Override + public void phaseStarted(AbstractPhaseScope phaseScope) { + super.phaseStarted(phaseScope); + this.solverScope = phaseScope.getSolverScope(); + } + + @Override + public void phaseEnded(AbstractPhaseScope phaseScope) { + super.phaseEnded(phaseScope); + this.solverScope = null; + } + @Override public void constructCache(SolverScope solverScope) { + if (cachedEntityList != null) { + return; + } super.constructCache(solverScope); sorter.sort(solverScope.getScoreDirector(), cachedEntityList); logger.trace(" Sorted cachedEntityList: size ({}), entitySelector ({}).", @@ -36,18 +70,27 @@ public boolean isNeverEnding() { return false; } + @Override + public long getSize() { + ensureStepCacheIsLoaded(); + return super.getSize(); + } + @Override public Iterator iterator() { + ensureStepCacheIsLoaded(); return cachedEntityList.iterator(); } @Override public ListIterator listIterator() { + ensureStepCacheIsLoaded(); return cachedEntityList.listIterator(); } @Override public ListIterator listIterator(int index) { + ensureStepCacheIsLoaded(); return cachedEntityList.listIterator(index); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java index dee2ffe252..137b941beb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java @@ -1,6 +1,5 @@ package ai.timefold.solver.core.impl.heuristic.selector.list; -import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY; import static ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.NONE; import java.util.Objects; @@ -43,30 +42,17 @@ public DestinationSelector buildDestinationSelector(HeuristicConfigPo && configPolicy.getEntitySorterManner() != NONE; var entityDescriptor = deduceEntityDescriptor(configPolicy, entitySelectorConfig.getEntityClass()); var hasDifficultySorter = entityDescriptor.getDecreasingDifficultySorter() != null; - var isEntityRangeSortingValid = - // no entity value range, so we accept any sorting manner - entityValueRangeRecorderId == null - // the entity value range is specified - // we only accept DECREASING_DIFFICULTY, - // indicating that the configuration requires sorting - || hasSortManner && configPolicy.getEntitySorterManner() == DECREASING_DIFFICULTY; - - if (hasSortManner && hasDifficultySorter && isEntityRangeSortingValid - && entitySelectorConfig.getSorterManner() == null) { - entitySelectorConfig.setCacheType(SelectionCacheType.PHASE); + if (hasSortManner && hasDifficultySorter && entitySelectorConfig.getSorterManner() == null) { + if (entityValueRangeRecorderId == null) { + // Solution-range model + entitySelectorConfig.setCacheType(SelectionCacheType.PHASE); + } else { + // Entity-range model requires the sorting to be done in each step + entitySelectorConfig.setCacheType(SelectionCacheType.STEP); + } entitySelectorConfig.setSelectionOrder(SelectionOrder.SORTED); entitySelectorConfig.setSorterManner(configPolicy.getEntitySorterManner()); } - if (entityValueRangeRecorderId != null && entitySelectorConfig.getSelectionOrder() != null - && entitySelectorConfig.getSelectionOrder() == SelectionOrder.SORTED) { - // Sorting entities is not permitted when using an entity value range, - // as the list of reachable entities is only generated after selecting a value. - // This process prevents sorting and caching at the phase level. - throw new IllegalStateException(""" - The destination selector cannot to sort the entity list when an entity value range is used. - Maybe remove the setting "EntitySorterManner" from the phase config. - Maybe remove the entity selector sorting settings from the destination config."""); - } var entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder, new ValueRangeRecorderId(entityValueRangeRecorderId, false)); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index b1676c1556..72749834c1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -60,7 +60,7 @@ public MoveSelector buildMoveSelector(HeuristicConfigPolicy buildValueSelector(HeuristicConfigPolicy e.getValue() == null)).isEmpty(); } - private static List generateConstructionHeuristicTestValues() { + private static List generateConstructionHeuristicSimpleConfiguration() { var values = new ArrayList(); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() @@ -365,7 +365,9 @@ private static List generateConstructionHeurist ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // the entities are being read in decreasing order of difficulty, // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Only the entities are sorted, and shuffling the values will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT) @@ -373,7 +375,9 @@ private static List generateConstructionHeurist ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // the values are being read in increase order of strength, // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT_DECREASING) @@ -382,7 +386,9 @@ private static List generateConstructionHeurist // the entities are being read in decreasing order of difficulty, // and the values are being read in increase order of strength // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT) @@ -390,7 +396,9 @@ private static List generateConstructionHeurist ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // and the values are being read in decreasing order of strength // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT_DECREASING) @@ -399,7 +407,9 @@ private static List generateConstructionHeurist // the entities are being read in decreasing order of difficulty, // and the values are being read in decreasing order of strength // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); // Allocate from pool // Simple configuration values.add(new ConstructionHeuristicTestConfig( @@ -412,7 +422,9 @@ private static List generateConstructionHeurist // Since we are starting from decreasing strength // and the entities are being read in decreasing order of difficulty, // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -421,7 +433,9 @@ private static List generateConstructionHeurist .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -430,7 +444,9 @@ private static List generateConstructionHeurist .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -439,7 +455,9 @@ private static List generateConstructionHeurist .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -448,7 +466,9 @@ private static List generateConstructionHeurist .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -457,7 +477,15 @@ private static List generateConstructionHeurist .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); + return values; + } + + private static List + generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { + var values = new ArrayList(); // Advanced configuration values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() @@ -474,14 +502,26 @@ private static List generateConstructionHeurist .withValueSelectorConfig(new ValueSelectorConfig()) .withEntitySelectorConfig(new EntitySelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) + .withCacheType(entityDestinationCacheType) .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))) .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // Since we are starting from decreasing strength // and the entities are being read in decreasing order of difficulty, // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); + var nonSortedEntityConfig = new EntitySelectorConfig(); + var isPhaseScope = entityDestinationCacheType == SelectionCacheType.PHASE; + if (isPhaseScope) { + // Hack to prevent the default sorting option, + // which is DECREASING_DIFFICULTY_IF_AVAILABLE + // This hack does not work with STEP scope + nonSortedEntityConfig.setSorterManner(EntitySorterManner.NONE); + nonSortedEntityConfig.setSelectionOrder(SelectionOrder.SORTED); + nonSortedEntityConfig.setCacheType(entityDestinationCacheType); + } values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -495,16 +535,14 @@ private static List generateConstructionHeurist new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) .withDestinationSelectorConfig(new DestinationSelectorConfig() .withValueSelectorConfig(new ValueSelectorConfig()) - .withEntitySelectorConfig(new EntitySelectorConfig() - // Hack to prevent the default sorting option, - // which is DECREASING_DIFFICULTY_IF_AVAILABLE - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.NONE))))) + .withEntitySelectorConfig(nonSortedEntityConfig)))) .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + // The step scope will apply the default entity sort manner + isPhaseScope ? new int[] { 2, 1, 0 } : new int[] { 0, 1, 2 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -520,12 +558,14 @@ private static List generateConstructionHeurist .withValueSelectorConfig(new ValueSelectorConfig()) .withEntitySelectorConfig(new EntitySelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) + .withCacheType(entityDestinationCacheType) .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))) .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -539,21 +579,26 @@ private static List generateConstructionHeurist new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) .withDestinationSelectorConfig(new DestinationSelectorConfig() .withValueSelectorConfig(new ValueSelectorConfig()) - .withEntitySelectorConfig(new EntitySelectorConfig() - // Hack to prevent the default sorting option, - // which is DECREASING_DIFFICULTY_IF_AVAILABLE - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.NONE))))) + .withEntitySelectorConfig(nonSortedEntityConfig)))) .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + // The step scope will apply the default entity sort manner + isPhaseScope ? new int[] { 0, 1, 2 } : new int[] { 2, 1, 0 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); + return values; + } + + private static List generateConstructionHeuristicConfiguration() { + var values = new ArrayList(); + values.addAll(generateConstructionHeuristicSimpleConfiguration()); + values.addAll(generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); return values; } @ParameterizedTest - @MethodSource("generateConstructionHeuristicTestValues") + @MethodSource("generateConstructionHeuristicConfiguration") void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils @@ -562,20 +607,25 @@ void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHe .withEasyScoreCalculatorClass(OneValuePerEntityEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataListSortableSolution.generateSolution(3, 3); + var solution = TestdataListSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); - for (var i = 0; i < 3; i++) { - // The calculator will give a better score for an entity with only one value. - assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) - .isEqualTo(phaseConfig.expected[i]); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValueList()).hasSize(1); + assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + } } } @ParameterizedTest - @MethodSource("generateConstructionHeuristicTestValues") + @MethodSource("generateConstructionHeuristicConfiguration") void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils @@ -584,106 +634,32 @@ void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuri .withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataListFactorySortableSolution.generateSolution(3, 3); + var solution = TestdataListFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); - for (var i = 0; i < 3; i++) { - // The calculator will give a better score for an entity with only one value. - assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) - .isEqualTo(phaseConfig.expected[i]); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValueList()).hasSize(1); + assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + } } } - private static List generateEntityRangeConstructionHeuristicTestValues() { + private static List generateEntityRangeConstructionHeuristicConfiguration() { var values = new ArrayList(); - // Simple configuration - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // Since we are starting from decreasing strength - // and the entities cannot be sorted, - // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.NONE) - .withValueSorterManner(ValueSorterManner.NONE) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // The order is not guaranteed - null)); - // Advanced configuration - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfig(new QueuedValuePlacerConfig() - .withValueSelectorConfig(new ValueSelectorConfig() - .withId("sortedValueSelector") - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig( - new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) - .withDestinationSelectorConfig(new DestinationSelectorConfig()))) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 })); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfig(new QueuedValuePlacerConfig() - .withValueSelectorConfig(new ValueSelectorConfig() - .withId("sortedValueSelector") - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig( - new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) - .withDestinationSelectorConfig(new DestinationSelectorConfig()))) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 })); + values.addAll(generateConstructionHeuristicSimpleConfiguration()); + values.addAll(generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType.STEP)); return values; } @ParameterizedTest - @MethodSource("generateEntityRangeConstructionHeuristicTestValues") + @MethodSource("generateEntityRangeConstructionHeuristicConfiguration") void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = @@ -694,22 +670,25 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3); + var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); if (phaseConfig.expected() != null) { for (var i = 0; i < 3; i++) { - // The calculator will give a better score for an entity with only one value. - assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) - .isEqualTo(phaseConfig.expected[i]); + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValueList()).hasSize(1); + assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); } } } @ParameterizedTest - @MethodSource("generateEntityRangeConstructionHeuristicTestValues") + @MethodSource("generateEntityRangeConstructionHeuristicConfiguration") void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils @@ -719,79 +698,55 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(Cons .withEasyScoreCalculatorClass(OneValuePerEntityRangeFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataListFactorySortableEntityProvidingSolution.generateSolution(3, 3); + var solution = TestdataListFactorySortableEntityProvidingSolution.generateSolution(3, 3, + phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); if (phaseConfig.expected() != null) { for (var i = 0; i < 3; i++) { - // The calculator will give a better score for an entity with only one value. - assertThat(solution.getEntityList().get(i).getValueList()).hasSize(1); - assertThat(solution.getEntityList().get(i).getValueList().get(0).getStrength()) - .isEqualTo(phaseConfig.expected[i]); + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValueList()).hasSize(1); + assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); } } } - private static List generateFailingConstructionHeuristicTestValues() { - // Entity sorting will throw errors - var values = new ArrayList(); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT_DECREASING), - null)); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.WEAKEST_FIT_DECREASING), - null)); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.STRONGEST_FIT_DECREASING), - null)); - // Allocate from pool - // Simple configuration - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) - .withEntitySorterManner(EntitySorterManner.DECREASING_DIFFICULTY) - .withValueSorterManner(ValueSorterManner.DECREASING_STRENGTH), - null)); - // Advanced configuration - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfig(new QueuedValuePlacerConfig() - .withValueSelectorConfig(new ValueSelectorConfig() - .withId("sortedValueSelector") - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) - .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() - .withValueSelectorConfig( - new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) - .withDestinationSelectorConfig(new DestinationSelectorConfig() - .withValueSelectorConfig(new ValueSelectorConfig()) - .withEntitySelectorConfig(new EntitySelectorConfig() - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY))))), - null)); - return values; - } - - @ParameterizedTest - @MethodSource("generateFailingConstructionHeuristicTestValues") - void failConstructionHeuristicEntityRangeAllocateToValueFromQueue(ConstructionHeuristicTestConfig phaseConfig) { + @Test + void failConstructionHeuristicEntityRange() { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, TestdataListSortableEntityProvidingEntity.class, TestdataListSortableEntityProvidingValue.class) .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) - .withPhases(phaseConfig.config()); - var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3); + .withPhases( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner( + EntitySorterManner.DECREASING_DIFFICULTY)))))); + var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3, true); assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) - .hasMessageContaining( - "The destination selector cannot to sort the entity list when an entity value range is used."); + .hasMessageContaining("resolvedSelectionOrder (SORTED) which does not support the resolvedCacheType (PHASE)") + .hasMessageContaining("Maybe set the \"cacheType\" to STEP."); } @Test @@ -805,7 +760,7 @@ void failMixedModelDefaultConfiguration() { "has both basic and list variables and cannot be deduced automatically"); } - private record ConstructionHeuristicTestConfig(ConstructionHeuristicPhaseConfig config, int[] expected) { + private record ConstructionHeuristicTestConfig(ConstructionHeuristicPhaseConfig config, int[] expected, boolean shuffle) { } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java index b2a3fa7959..5042a72764 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java @@ -1,6 +1,9 @@ package ai.timefold.solver.core.testdomain.list.sort.comparator; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Random; import java.util.stream.IntStream; import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; @@ -20,13 +23,18 @@ public static SolutionDescriptor buildSolutionDesc TestdataListSortableValue.class); } - public static TestdataListSortableSolution generateSolution(int valueCount, int entityCount) { - var entityList = IntStream.range(0, entityCount) + public static TestdataListSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) .mapToObj(i -> new TestdataListSortableEntity("Generated Entity " + i, i)) - .toList(); - var valueList = IntStream.range(0, valueCount) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataListSortableValue("Generated Value " + i, i)) - .toList(); + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } TestdataListSortableSolution solution = new TestdataListSortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java index a21fc04245..f5d77f582d 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java @@ -1,6 +1,9 @@ package ai.timefold.solver.core.testdomain.list.sort.factory; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Random; import java.util.stream.IntStream; import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; @@ -20,13 +23,18 @@ public static SolutionDescriptor buildSolut TestdataListFactorySortableValue.class); } - public static TestdataListFactorySortableSolution generateSolution(int valueCount, int entityCount) { - var entityList = IntStream.range(0, entityCount) + public static TestdataListFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) .mapToObj(i -> new TestdataListFactorySortableEntity("Generated Entity " + i, i)) - .toList(); - var valueList = IntStream.range(0, valueCount) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataListFactorySortableValue("Generated Value " + i, i)) - .toList(); + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } TestdataListFactorySortableSolution solution = new TestdataListFactorySortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java index 9ae9f9b076..8e5e9b12b4 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java @@ -22,20 +22,26 @@ public static SolutionDescriptor bu TestdataListSortableEntityProvidingValue.class); } - public static TestdataListSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount) { - var entityList = IntStream.range(0, entityCount) + public static TestdataListSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) .mapToObj(i -> new TestdataListSortableEntityProvidingEntity("Generated Entity " + i, i)) - .toList(); + .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataListSortableEntityProvidingValue("Generated Value " + i, i)) .toList(); - var solution = new TestdataListSortableEntityProvidingSolution(); var random = new Random(0); + var solution = new TestdataListSortableEntityProvidingSolution(); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); - Collections.shuffle(valueRange, random); + if (shuffle) { + Collections.shuffle(valueRange, random); + } entity.setValueRange(valueRange); } + if (shuffle) { + Collections.shuffle(entityList, random); + } solution.setEntityList(entityList); return solution; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java index 1ed9d7f271..8baa3b9968 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java @@ -22,10 +22,11 @@ public static SolutionDescriptor(IntStream.range(0, entityCount) .mapToObj(i -> new TestdataListFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) - .toList(); + .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataListFactorySortableEntityProvidingValue("Generated Value " + i, i)) .toList(); @@ -33,9 +34,14 @@ public static TestdataListFactorySortableEntityProvidingSolution generateSolutio var random = new Random(0); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); - Collections.shuffle(valueRange, random); + if (shuffle) { + Collections.shuffle(valueRange, random); + } entity.setValueRange(valueRange); } + if (shuffle) { + Collections.shuffle(entityList, random); + } solution.setEntityList(entityList); return solution; } From 9ed8ead1605621e6d6bef3cc11b99f62da601547 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 26 Sep 2025 17:27:05 -0300 Subject: [PATCH 09/33] chore: migration recipe --- migration/src/main/resources/META-INF/rewrite/ToLatest.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index f62aa8f3ef..df0da45138 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -36,3 +36,7 @@ recipeList: - ai.timefold.solver.migration.v8.SolutionManagerRecommendAssignmentRecipe - org.openrewrite.java.RemoveUnusedImports - ai.timefold.solver.migration.ChangeVersion + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.SorterWeightFactory + ignoreDefinition: true From 59ad835a2f5b84095584f112e772951344ec2219 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 26 Sep 2025 17:58:56 -0300 Subject: [PATCH 10/33] chore: address comments --- .../api/domain/common/SorterWeightFactory.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java index 02c9b0d82a..9bd51fd4a6 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java @@ -2,6 +2,9 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; +import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; @@ -12,13 +15,22 @@ * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). * The selections are then sorted by their weight, * normally ascending unless it's configured descending. - * + * The property {@code sortManner}, + * present in the selector configurations such as {@link ValueSelectorConfig} and {@link EntitySelectorConfig}, + * specifies how the data will be sorted. + * Additionally, + * the property {@code constructionHeuristicType} from {@link ConstructionHeuristicPhaseConfig} can also configure how entities + * and values are sorted. *

* Implementations are expected to be stateless. * The solver may choose to reuse instances. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type + * + * @see ValueSelectorConfig + * @see EntitySelectorConfig + * @see ConstructionHeuristicPhaseConfig */ @NullMarked @FunctionalInterface From a18f31b8f833c064d60d70942004995fc955078e Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 10 Oct 2025 16:11:18 -0300 Subject: [PATCH 11/33] feat: adjust list variable sorting fields --- core/src/build/revapi-differences.json | 20 ++++----- ...rWeightFactory.java => SorterFactory.java} | 8 ++-- .../api/domain/entity/PlanningEntity.java | 8 ++-- .../domain/variable/PlanningListVariable.java | 40 ++++++++--------- .../api/domain/variable/PlanningVariable.java | 16 ++++--- .../selector/entity/EntitySelectorConfig.java | 13 +++--- .../selector/move/MoveSelectorConfig.java | 10 ++--- .../selector/value/ValueSelectorConfig.java | 10 ++--- .../entity/descriptor/EntityDescriptor.java | 4 +- .../descriptor/BasicVariableDescriptor.java | 4 +- .../descriptor/GenuineVariableDescriptor.java | 43 +++++++++---------- .../descriptor/ListVariableDescriptor.java | 4 +- ...orter.java => SelectionFactorySorter.java} | 30 ++++++------- .../SelectionSorterWeightFactory.java | 16 +++++-- .../entity/EntitySelectorFactory.java | 8 ++-- .../move/AbstractMoveSelectorFactory.java | 8 ++-- .../selector/value/ValueSelectorFactory.java | 8 ++-- ...DefaultConstructionHeuristicPhaseTest.java | 9 +--- ...t.java => SelectionFactorySorterTest.java} | 12 +++--- .../entity/EntitySelectorFactoryTest.java | 10 ++--- .../value/ValueSelectorFactoryTest.java | 10 ++--- ...ry.java => TestdataDifficultyFactory.java} | 8 ++-- .../TestdataDifficultyWeightEntity.java | 2 +- .../TestdataListSortableEntity.java | 2 +- .../factory/ListSortableEntityFactory.java | 6 +-- .../factory/ListSortableValueFactory.java | 6 +-- .../TestdataListFactorySortableEntity.java | 2 +- ...dataListSortableEntityProvidingEntity.java | 2 +- .../factory/ListSortableEntityFactory.java | 6 +-- .../factory/ListSortableValueFactory.java | 6 +-- ...tFactorySortableEntityProvidingEntity.java | 2 +- .../optimization-algorithms/overview.adoc | 10 ++--- .../resources/META-INF/rewrite/ToLatest.yml | 10 +++-- 33 files changed, 178 insertions(+), 175 deletions(-) rename core/src/main/java/ai/timefold/solver/core/api/domain/common/{SorterWeightFactory.java => SorterFactory.java} (88%) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/{WeightFactorySelectionSorter.java => SelectionFactorySorter.java} (65%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/{WeightFactorySelectionSorterTest.java => SelectionFactorySorterTest.java} (76%) rename core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/{TestdataDifficultyWeightFactory.java => TestdataDifficultyFactory.java} (57%) diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index f91d3e2310..6d162e8d46 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -353,44 +353,44 @@ "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", "justification": "New weight factory base class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", "justification": "New weight factory base class" }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", + "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", "justification": "New weight factory base class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", - "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", "justification": "New weight factory base class" }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", - "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", + "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", "justification": "New weight factory base class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", - "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", + "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", "justification": "New weight factory base class" } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java similarity index 88% rename from core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java rename to core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java index 9bd51fd4a6..75c6fd8497 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java @@ -11,9 +11,9 @@ import org.jspecify.annotations.NullMarked; /** - * Creates a weight to decide the order of a collections of selections + * Creates a {@link Comparable} to decide the order of a collection of selections * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). - * The selections are then sorted by their weight, + * The selections are then sorted by some specific metric, * normally ascending unless it's configured descending. * The property {@code sortManner}, * present in the selector configurations such as {@link ValueSelectorConfig} and {@link EntitySelectorConfig}, @@ -34,13 +34,13 @@ */ @NullMarked @FunctionalInterface -public interface SorterWeightFactory { +public interface SorterFactory { /** * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} */ - Comparable createSorterWeight(Solution_ solution, T selection); + Comparable createSorter(Solution_ solution, T selection); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index a53f6bb3f7..a1e6994b89 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -7,7 +7,7 @@ import java.lang.annotation.Target; import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -79,17 +79,17 @@ interface NullDifficultyComparator extends Comparator { } /** - * The {@link SorterWeightFactory} alternative for {@link #difficultyComparatorClass()}. + * The {@link SorterFactory} alternative for {@link #difficultyComparatorClass()}. *

* Do not use together with {@link #difficultyComparatorClass()}. * * @return {@link NullDifficultyWeightFactory} when it is null (workaround for annotation limitation) * @see #difficultyComparatorClass() */ - Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; + Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */ - interface NullDifficultyWeightFactory extends SorterWeightFactory { + interface NullDifficultyWeightFactory extends SorterFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java index 5c73946912..1c3ed35178 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java @@ -9,10 +9,12 @@ import java.util.Comparator; import java.util.List; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; 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.PlanningVariable.NullComparator; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory; /** * Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms. @@ -58,36 +60,28 @@ String[] valueRangeProviderRefs() default {}; /** - * Allows a collection of planning values for this variable to be sorted by strength. - * A strengthWeight estimates how strong a planning value is. - * Some algorithms benefit from planning on weaker planning values first or from focusing on them. + * Allows sorting a collection of planning values for this variable. + * Some algorithms perform better when the values are sorted based on specific metrics. *

- * The {@link Comparator} should sort in ascending strength. - * For example: sorting 3 computers on strength based on their RAM capacity: - * Computer B (1GB RAM), Computer A (2GB RAM), Computer C (7GB RAM), + * The {@link Comparator} should sort the data in ascending order. + * For example, prioritize three visits by sorting them based on their importance: + * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY) *

- * Do not use together with {@link #strengthWeightFactoryClass()}. + * Do not use together with {@link #comparatorFactoryClass()}. * - * @return {@link PlanningVariable.NullStrengthComparator} when it is null (workaround for annotation limitation) - * @see #strengthWeightFactoryClass() + * @return {@link NullComparator} when it is null (workaround for annotation limitation) + * @see #comparatorFactoryClass() */ - Class strengthComparatorClass() default PlanningVariable.NullStrengthComparator.class; - - /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ - interface NullStrengthComparator extends Comparator { - } + Class comparatorClass() default NullComparator.class; /** - * The {@link SorterWeightFactory} alternative for {@link #strengthComparatorClass()}. + * The {@link SorterFactory} alternative for {@link #comparatorClass()}. *

- * Do not use together with {@link #strengthComparatorClass()}. + * Do not use together with {@link #comparatorClass()}. * - * @return {@link PlanningVariable.NullStrengthWeightFactory} when it is null (workaround for annotation limitation) - * @see #strengthComparatorClass() + * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) + * @see #comparatorClass() */ - Class strengthWeightFactoryClass() default PlanningVariable.NullStrengthWeightFactory.class; + Class comparatorFactoryClass() default NullComparatorFactory.class; - /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ - interface NullStrengthWeightFactory extends SorterWeightFactory { - } } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 6632772d16..5af5055ab6 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -8,10 +8,10 @@ import java.lang.annotation.Target; import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms. @@ -84,21 +84,27 @@ Class strengthComparatorClass() default NullStrengthComparator.class; /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ - interface NullStrengthComparator extends Comparator { + interface NullStrengthComparator extends NullComparator { + } + + interface NullComparator extends Comparator { } /** - * The {@link SorterWeightFactory} alternative for {@link #strengthComparatorClass()}. + * The {@link SelectionSorterWeightFactory} alternative for {@link #strengthComparatorClass()}. *

* Do not use together with {@link #strengthComparatorClass()}. * * @return {@link NullStrengthWeightFactory} when it is null (workaround for annotation limitation) * @see #strengthComparatorClass() */ - Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; + Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ - interface NullStrengthWeightFactory extends SorterWeightFactory { + interface NullStrengthWeightFactory extends NullComparatorFactory { + } + + interface NullComparatorFactory extends SelectionSorterWeightFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index e6f889b4c1..1ae1fa357a 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -63,7 +63,7 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe protected EntitySorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -156,11 +156,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -247,7 +247,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull EntitySelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } @@ -350,8 +350,7 @@ public static boolean hasSorter(@NonNull EntitySorterManner entitySo switch (entitySorterManner) { case NONE: throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); - case DECREASING_DIFFICULTY: - case DECREASING_DIFFICULTY_IF_AVAILABLE: + case DECREASING_DIFFICULTY, DECREASING_DIFFICULTY_IF_AVAILABLE: sorter = (SelectionSorter) entityDescriptor.getDecreasingDifficultySorter(); if (sorter == null) { throw new IllegalArgumentException("The sorterManner (" + entitySorterManner diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index e05a5b42a5..7afbd80590 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlSeeAlso; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -82,7 +82,7 @@ public abstract class MoveSelectorConfig filterClass = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -128,11 +128,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -202,7 +202,7 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { } public @NonNull Config_ withSorterWeightFactoryClass( - @NonNull Class sorterWeightFactoryClass) { + @NonNull Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index 9a83d07f72..e7f13eeabf 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -61,7 +61,7 @@ public class ValueSelectorConfig extends SelectorConfig { protected ValueSorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -162,11 +162,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -258,7 +258,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull ValueSelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } 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 b49e018d32..2fde2ddec2 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 @@ -64,8 +64,8 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.move.director.MoveDirector; import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; import ai.timefold.solver.core.impl.util.CollectionUtils; @@ -290,7 +290,7 @@ private void processDifficulty(PlanningEntity entityAnnotation) { if (difficultyWeightFactoryClass != null) { var difficultyWeightFactory = ConfigUtils.newInstance(this::toString, "difficultyWeightFactoryClass", difficultyWeightFactoryClass); - decreasingDifficultySorter = new WeightFactorySelectionSorter<>( + decreasingDifficultySorter = new SelectionFactorySorter<>( difficultyWeightFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java index 8972425907..18abcb3892 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java @@ -41,8 +41,8 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) { processAllowsUnassigned(planningVariableAnnotation); processChained(planningVariableAnnotation); processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs()); - processStrength(planningVariableAnnotation.strengthComparatorClass(), - planningVariableAnnotation.strengthWeightFactoryClass()); + processSorting("strengthComparatorClass", planningVariableAnnotation.strengthComparatorClass(), + "strengthWeightFactoryClass", planningVariableAnnotation.strengthWeightFactoryClass()); } private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 7a8caee360..c36aaad97f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -8,7 +8,7 @@ import java.util.Comparator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; @@ -21,8 +21,8 @@ import ai.timefold.solver.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; /** * @param the solution type, the class with the {@link PlanningSolution} annotation @@ -156,35 +156,34 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli } @SuppressWarnings("rawtypes") - protected void processStrength(Class strengthComparatorClass, - Class strengthWeightFactoryClass) { - if (strengthComparatorClass == PlanningVariable.NullStrengthComparator.class) { - strengthComparatorClass = null; + protected void processSorting(String comparatorPropertyName, Class comparatorClass, + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) { + comparatorClass = null; } - if (strengthWeightFactoryClass == PlanningVariable.NullStrengthWeightFactory.class) { - strengthWeightFactoryClass = null; + if (comparatorFactoryClass != null + && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) { + comparatorFactoryClass = null; } - if (strengthComparatorClass != null && strengthWeightFactoryClass != null) { - throw new IllegalStateException("The entityClass (" + entityDescriptor.getEntityClass() - + ") property (" + variableMemberAccessor.getName() - + ") cannot have a strengthComparatorClass (" + strengthComparatorClass.getName() - + ") and a strengthWeightFactoryClass (" + strengthWeightFactoryClass.getName() - + ") at the same time."); + if (comparatorClass != null && comparatorFactoryClass != null) { + throw new IllegalStateException( + "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted( + entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), comparatorPropertyName, + comparatorClass.getName(), comparatorFactoryPropertyName, comparatorFactoryClass.getName())); } - if (strengthComparatorClass != null) { - Comparator strengthComparator = newInstance(this::toString, - "strengthComparatorClass", strengthComparatorClass); + if (comparatorClass != null) { + Comparator strengthComparator = newInstance(this::toString, comparatorPropertyName, comparatorClass); increasingStrengthSorter = new ComparatorSelectionSorter<>(strengthComparator, SelectionSorterOrder.ASCENDING); decreasingStrengthSorter = new ComparatorSelectionSorter<>(strengthComparator, SelectionSorterOrder.DESCENDING); } - if (strengthWeightFactoryClass != null) { - SorterWeightFactory strengthWeightFactory = newInstance(this::toString, - "strengthWeightFactoryClass", strengthWeightFactoryClass); - increasingStrengthSorter = new WeightFactorySelectionSorter<>(strengthWeightFactory, + if (comparatorFactoryClass != null) { + SorterFactory strengthWeightFactory = + newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); + increasingStrengthSorter = new SelectionFactorySorter<>(strengthWeightFactory, SelectionSorterOrder.ASCENDING); - decreasingStrengthSorter = new WeightFactorySelectionSorter<>(strengthWeightFactory, + decreasingStrengthSorter = new SelectionFactorySorter<>(strengthWeightFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java index aedb87a7f8..76b85c54f8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java @@ -60,8 +60,8 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) { PlanningListVariable planningVariableAnnotation = variableMemberAccessor.getAnnotation(PlanningListVariable.class); allowsUnassignedValues = planningVariableAnnotation.allowsUnassignedValues(); processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs()); - processStrength(planningVariableAnnotation.strengthComparatorClass(), - planningVariableAnnotation.strengthWeightFactoryClass()); + processSorting("comparatorClass", planningVariableAnnotation.comparatorClass(), "comparatorFactoryClass", + planningVariableAnnotation.comparatorFactoryClass()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java similarity index 65% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java index 85445fa75d..df70a15394 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java @@ -7,7 +7,7 @@ import java.util.SortedMap; import java.util.TreeMap; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; @@ -16,25 +16,25 @@ import ai.timefold.solver.core.impl.heuristic.selector.Selector; /** - * Sorts a selection {@link List} based on a {@link SorterWeightFactory}. + * Sorts a selection {@link List} based on a {@link SorterFactory}. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ -public final class WeightFactorySelectionSorter implements SelectionSorter { +public final class SelectionFactorySorter implements SelectionSorter { - private final SorterWeightFactory selectionSorterWeightFactory; - private final Comparator appliedWeightComparator; + private final SorterFactory selectionSorterFactory; + private final Comparator comparator; - public WeightFactorySelectionSorter(SorterWeightFactory selectionSorterWeightFactory, + public SelectionFactorySorter(SorterFactory selectionSorterFactory, SelectionSorterOrder selectionSorterOrder) { - this.selectionSorterWeightFactory = selectionSorterWeightFactory; + this.selectionSorterFactory = selectionSorterFactory; switch (selectionSorterOrder) { case ASCENDING: - this.appliedWeightComparator = Comparator.naturalOrder(); + this.comparator = Comparator.naturalOrder(); break; case DESCENDING: - this.appliedWeightComparator = Collections.reverseOrder(); + this.comparator = Collections.reverseOrder(); break; default: throw new IllegalStateException("The selectionSorterOrder (" + selectionSorterOrder @@ -53,9 +53,9 @@ public void sort(ScoreDirector scoreDirector, List selectionList) * of {@link PlanningEntity}, planningValue, {@link Move} or {@link Selector} */ public void sort(Solution_ solution, List selectionList) { - SortedMap selectionMap = new TreeMap<>(appliedWeightComparator); + SortedMap selectionMap = new TreeMap<>(comparator); for (T selection : selectionList) { - Comparable difficultyWeight = selectionSorterWeightFactory.createSorterWeight(solution, selection); + Comparable difficultyWeight = selectionSorterFactory.createSorter(solution, selection); T previous = selectionMap.put(difficultyWeight, selection); if (previous != null) { throw new IllegalStateException("The selectionList contains 2 times the same selection (" @@ -72,13 +72,13 @@ public boolean equals(Object other) { return true; if (other == null || getClass() != other.getClass()) return false; - WeightFactorySelectionSorter that = (WeightFactorySelectionSorter) other; - return Objects.equals(selectionSorterWeightFactory, that.selectionSorterWeightFactory) - && Objects.equals(appliedWeightComparator, that.appliedWeightComparator); + SelectionFactorySorter that = (SelectionFactorySorter) other; + return Objects.equals(selectionSorterFactory, that.selectionSorterFactory) + && Objects.equals(comparator, that.comparator); } @Override public int hashCode() { - return Objects.hash(selectionSorterWeightFactory, appliedWeightComparator); + return Objects.hash(selectionSorterFactory, comparator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 6077981eb6..13d40923ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.heuristic.move.Move; @@ -16,13 +16,21 @@ * Implementations are expected to be stateless. * The solver may choose to reuse instances. * - * @deprecated Deprecated in favor of {@link SorterWeightFactory}. + * @deprecated Deprecated in favor of {@link SorterFactory}. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ @Deprecated(forRemoval = true, since = "1.27.0") -@FunctionalInterface -public interface SelectionSorterWeightFactory extends SorterWeightFactory { +public interface SelectionSorterWeightFactory extends SorterFactory { + Comparable createSorterWeight(Solution_ solution, T selection); + + /** + * The default implementation has been created to maintain compatibility with the old contract. + */ + @Override + default Comparable createSorter(Solution_ solution, T selection) { + return createSorterWeight(solution, selection); + } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index f29ac2b36d..7bc1079bc1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -6,7 +6,7 @@ import java.util.function.Function; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -20,10 +20,10 @@ import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.ValueRangeRecorderId; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByValueSelector; @@ -316,9 +316,9 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SorterWeightFactory sorterWeightFactory = + SorterFactory sorterFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, + sorter = new SelectionFactorySorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 72749834c1..a4241c3cd5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -2,7 +2,7 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; 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.common.decorator.SelectionSorterOrder; @@ -12,10 +12,10 @@ import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.CachingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector; @@ -195,9 +195,9 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterWeightFactoryClass != null) { - SorterWeightFactory> sorterWeightFactory = + SorterFactory> sorterFactory = ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); - sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, + sorter = new SelectionFactorySorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 3b28513bd6..74c53bffbf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.function.Function; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -19,10 +19,10 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.CachingValueSelector; @@ -336,9 +336,9 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SorterWeightFactory sorterWeightFactory = + SorterFactory sorterFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, + sorter = new SelectionFactorySorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); 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 dad76e05a2..822f7e27be 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 @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,17 +25,9 @@ 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.heuristic.selector.value.ValueSorterManner; -import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig; -import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; -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; 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.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.sort.comparator.OneValuePerEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableEntity; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java similarity index 76% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java index 3dbf62f8e8..a37a8160b4 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/WeightFactorySelectionSorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import java.util.List; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import ai.timefold.solver.core.testdomain.TestdataEntity; @@ -14,13 +14,13 @@ import org.junit.jupiter.api.Test; -class WeightFactorySelectionSorterTest { +class SelectionFactorySorterTest { @Test void sortAscending() { - SorterWeightFactory weightFactory = (solution, selection) -> Integer + SorterFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); - WeightFactorySelectionSorter selectionSorter = new WeightFactorySelectionSorter<>( + SelectionFactorySorter selectionSorter = new SelectionFactorySorter<>( weightFactory, SelectionSorterOrder.ASCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); @@ -34,9 +34,9 @@ void sortAscending() { @Test void sortDescending() { - SorterWeightFactory weightFactory = (solution, selection) -> Integer + SorterFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); - WeightFactorySelectionSorter selectionSorter = new WeightFactorySelectionSorter<>( + SelectionFactorySorter selectionSorter = new SelectionFactorySorter<>( weightFactory, SelectionSorterOrder.DESCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 3c2d3b5d51..a8c412d730 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -7,7 +7,7 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -156,7 +156,7 @@ void applySorting_withSorterComparatorClass() { @Test void applySorting_withSorterWeightFactoryClass() { EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() - .withSorterWeightFactoryClass(DummySelectionSorterWeightFactory.class); + .withSorterWeightFactoryClass(DummySelectionSorterFactory.class); applySorting(entitySelectorConfig); } @@ -207,10 +207,10 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionSorterWeightFactory - implements SorterWeightFactory { + public static class DummySelectionSorterFactory + implements SorterFactory { @Override - public Comparable createSorterWeight(TestdataSolution testdataSolution, TestdataEntity selection) { + public Comparable createSorter(TestdataSolution testdataSolution, TestdataEntity selection) { return 0; } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index d413f6a86a..1939608068 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -11,7 +11,7 @@ import java.util.Iterator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -218,7 +218,7 @@ void applySorting_withSorterComparatorClass() { @Test void applySorting_withSorterWeightFactoryClass() { ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() - .withSorterWeightFactoryClass(DummySelectionSorterWeightFactory.class); + .withSorterWeightFactoryClass(DummySelectionSorterFactory.class); applySorting(valueSelectorConfig); } @@ -310,10 +310,10 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionSorterWeightFactory - implements SorterWeightFactory { + public static class DummySelectionSorterFactory + implements SorterFactory { @Override - public Comparable createSorterWeight(TestdataSolution testdataSolution, TestdataValue selection) { + public Comparable createSorter(TestdataSolution testdataSolution, TestdataValue selection) { return 0; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java similarity index 57% rename from core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java index fcc8e294fc..1dcf32571b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.testdomain.difficultyweight; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; -public class TestdataDifficultyWeightFactory implements - SorterWeightFactory { +public class TestdataDifficultyFactory implements + SorterFactory { @Override - public TestdataDifficultyWeightComparable createSorterWeight(TestdataDifficultyWeightSolution solution, + public TestdataDifficultyWeightComparable createSorter(TestdataDifficultyWeightSolution solution, TestdataDifficultyWeightEntity entity) { return new TestdataDifficultyWeightComparable(); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightEntity.java index 468d1a7092..802bb2a0d0 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyWeightEntity.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.testdomain.TestdataObject; -@PlanningEntity(difficultyWeightFactoryClass = TestdataDifficultyWeightFactory.class) +@PlanningEntity(difficultyWeightFactoryClass = TestdataDifficultyFactory.class) public class TestdataDifficultyWeightEntity extends TestdataObject { public static EntityDescriptor buildEntityDescriptor() { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java index 7e6c3ad06b..33db3128aa 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java @@ -10,7 +10,7 @@ @PlanningEntity(difficultyComparatorClass = ListSortableEntityComparator.class) public class TestdataListSortableEntity extends TestdataObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = ListSortableValueComparator.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = ListSortableValueComparator.class) private List valueList; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java index 084e236521..eec862fb69 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.testdomain.list.sort.factory; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; public class ListSortableEntityFactory - implements SorterWeightFactory { + implements SorterFactory { @Override - public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, + public Comparable createSorter(TestdataListFactorySortableSolution solution, TestdataListFactorySortableEntity selection) { return selection; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java index f29746a094..eff999cfaa 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.testdomain.list.sort.factory; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; public class ListSortableValueFactory - implements SorterWeightFactory { + implements SorterFactory { @Override - public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, + public Comparable createSorter(TestdataListFactorySortableSolution solution, TestdataListFactorySortableValue selection) { return selection; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java index 7494590369..9ec163c0de 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java @@ -10,7 +10,7 @@ @PlanningEntity(difficultyWeightFactoryClass = ListSortableEntityFactory.class) public class TestdataListFactorySortableEntity extends TestdataObject implements Comparable { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = ListSortableValueFactory.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = ListSortableValueFactory.class) private List valueList; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java index f04a5bd464..0c0a9304d3 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java @@ -12,7 +12,7 @@ @PlanningEntity(difficultyComparatorClass = ListSortableEntityComparator.class) public class TestdataListSortableEntityProvidingEntity extends TestdataObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = ListSortableValueComparator.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = ListSortableValueComparator.class) private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java index 4c5dd23494..46d8458f0f 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java @@ -1,13 +1,13 @@ package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; public class ListSortableEntityFactory implements - SorterWeightFactory { + SorterFactory { @Override - public Comparable createSorterWeight( + public Comparable createSorter( TestdataListFactorySortableEntityProvidingSolution solution, TestdataListFactorySortableEntityProvidingEntity selection) { return selection; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java index 2ce935f1fc..f009c9774a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java @@ -1,13 +1,13 @@ package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; -import ai.timefold.solver.core.api.domain.common.SorterWeightFactory; +import ai.timefold.solver.core.api.domain.common.SorterFactory; public class ListSortableValueFactory implements - SorterWeightFactory { + SorterFactory { @Override - public Comparable createSorterWeight( + public Comparable createSorter( TestdataListFactorySortableEntityProvidingSolution solution, TestdataListFactorySortableEntityProvidingValue selection) { return selection; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java index eabab053bb..a8b9f1c00c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java @@ -13,7 +13,7 @@ public class TestdataListFactorySortableEntityProvidingEntity extends TestdataObject implements Comparable { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = ListSortableValueFactory.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = ListSortableValueFactory.class) private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 8531214e76..b0ba421a2f 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1600,15 +1600,15 @@ The solver may choose to reuse them in different contexts. [#sortedSelectionBySelectionSorterWeightFactory] -===== Sorted selection by `SorterWeightFactory` +===== Sorted selection by `SorterFactory` -If you need the entire solution to sort a ``Selector``, use a `SorterWeightFactory` instead: +If you need the entire solution to sort a ``Selector``, use a `SorterFactory` instead: [source,java,options="nowrap"] ---- -public interface SorterWeightFactory { +public interface SorterFactory { - Comparable createSorterWeight(Solution_ solution, T selection); + Comparable createSorter(Solution_ solution, T selection); } ---- @@ -1627,7 +1627,7 @@ You'll also need to configure it (unless it's annotated on the domain model and [NOTE] ==== -`SorterWeightFactory` implementations are expected to be stateless. +`SorterFactory` implementations are expected to be stateless. The solver may choose to reuse them in different contexts. ==== diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index df0da45138..4e60410ca0 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -34,9 +34,13 @@ recipeList: - ai.timefold.solver.migration.v8.AsConstraintRecipe - ai.timefold.solver.migration.v8.RemoveConstraintPackageRecipe - ai.timefold.solver.migration.v8.SolutionManagerRecommendAssignmentRecipe - - org.openrewrite.java.RemoveUnusedImports - - ai.timefold.solver.migration.ChangeVersion + - org.openrewrite.java.ChangeMethodName: + matchOverrides: true + methodPattern: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory createSorterWeight(..) + newMethodName: createSorter - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.SorterWeightFactory + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.SorterFactory ignoreDefinition: true + - org.openrewrite.java.RemoveUnusedImports + - ai.timefold.solver.migration.ChangeVersion From c3c6208b46b41d6ab9416206ace0566a863f99aa Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 10 Oct 2025 16:18:18 -0300 Subject: [PATCH 12/33] chore: renaming --- .../selector/value/ValueSelectorConfig.java | 8 +++---- .../entity/descriptor/EntityDescriptor.java | 4 ++-- .../descriptor/GenuineVariableDescriptor.java | 24 +++++++++---------- ...orter.java => FactorySelectionSorter.java} | 8 +++---- .../entity/EntitySelectorFactory.java | 4 ++-- .../move/AbstractMoveSelectorFactory.java | 4 ++-- .../selector/value/ValueSelectorFactory.java | 4 ++-- ...t.java => FactorySelectionSorterTest.java} | 6 ++--- 8 files changed, 31 insertions(+), 31 deletions(-) rename core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/{SelectionFactorySorter.java => FactorySelectionSorter.java} (92%) rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/{SelectionFactorySorterTest.java => FactorySelectionSorterTest.java} (89%) diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index e7f13eeabf..ced152f717 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -349,9 +349,9 @@ public static boolean hasSorter(@NonNull ValueSorterManner valueSort case DECREASING_STRENGTH: return true; case INCREASING_STRENGTH_IF_AVAILABLE: - return variableDescriptor.getIncreasingStrengthSorter() != null; + return variableDescriptor.getAscendingSorter() != null; case DECREASING_STRENGTH_IF_AVAILABLE: - return variableDescriptor.getDecreasingStrengthSorter() != null; + return variableDescriptor.getDescendingSorter() != null; default: throw new IllegalStateException("The sorterManner (" + valueSorterManner + ") is not implemented."); @@ -366,11 +366,11 @@ public static boolean hasSorter(@NonNull ValueSorterManner valueSort throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); case INCREASING_STRENGTH: case INCREASING_STRENGTH_IF_AVAILABLE: - sorter = variableDescriptor.getIncreasingStrengthSorter(); + sorter = variableDescriptor.getAscendingSorter(); break; case DECREASING_STRENGTH: case DECREASING_STRENGTH_IF_AVAILABLE: - sorter = variableDescriptor.getDecreasingStrengthSorter(); + sorter = variableDescriptor.getDescendingSorter(); break; default: throw new IllegalStateException("The sorterManner (" 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 2fde2ddec2..9406de5008 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 @@ -64,7 +64,7 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; import ai.timefold.solver.core.impl.move.director.MoveDirector; import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; @@ -290,7 +290,7 @@ private void processDifficulty(PlanningEntity entityAnnotation) { if (difficultyWeightFactoryClass != null) { var difficultyWeightFactory = ConfigUtils.newInstance(this::toString, "difficultyWeightFactoryClass", difficultyWeightFactoryClass); - decreasingDifficultySorter = new SelectionFactorySorter<>( + decreasingDifficultySorter = new FactorySelectionSorter<>( difficultyWeightFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index c36aaad97f..6e6b1587ec 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -21,7 +21,7 @@ import ai.timefold.solver.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; /** @@ -30,8 +30,8 @@ public abstract class GenuineVariableDescriptor extends VariableDescriptor { private ValueRangeDescriptor valueRangeDescriptor; - private SelectionSorter increasingStrengthSorter; - private SelectionSorter decreasingStrengthSorter; + private SelectionSorter ascendingSorter; + private SelectionSorter descendingSorter; // ************************************************************************ // Constructors and simple getters/setters @@ -173,17 +173,17 @@ protected void processSorting(String comparatorPropertyName, Class strengthComparator = newInstance(this::toString, comparatorPropertyName, comparatorClass); - increasingStrengthSorter = new ComparatorSelectionSorter<>(strengthComparator, + ascendingSorter = new ComparatorSelectionSorter<>(strengthComparator, SelectionSorterOrder.ASCENDING); - decreasingStrengthSorter = new ComparatorSelectionSorter<>(strengthComparator, + descendingSorter = new ComparatorSelectionSorter<>(strengthComparator, SelectionSorterOrder.DESCENDING); } if (comparatorFactoryClass != null) { - SorterFactory strengthWeightFactory = + SorterFactory comparatorFactory = newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); - increasingStrengthSorter = new SelectionFactorySorter<>(strengthWeightFactory, + ascendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); - decreasingStrengthSorter = new SelectionFactorySorter<>(strengthWeightFactory, + descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); } } @@ -238,12 +238,12 @@ public boolean isReinitializable(Object entity) { return value == null; } - public SelectionSorter getIncreasingStrengthSorter() { - return increasingStrengthSorter; + public SelectionSorter getAscendingSorter() { + return ascendingSorter; } - public SelectionSorter getDecreasingStrengthSorter() { - return decreasingStrengthSorter; + public SelectionSorter getDescendingSorter() { + return descendingSorter; } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java similarity index 92% rename from core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java rename to core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index df70a15394..b1ef47fd2c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -21,13 +21,13 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ -public final class SelectionFactorySorter implements SelectionSorter { +public final class FactorySelectionSorter implements SelectionSorter { private final SorterFactory selectionSorterFactory; private final Comparator comparator; - public SelectionFactorySorter(SorterFactory selectionSorterFactory, - SelectionSorterOrder selectionSorterOrder) { + public FactorySelectionSorter(SorterFactory selectionSorterFactory, + SelectionSorterOrder selectionSorterOrder) { this.selectionSorterFactory = selectionSorterFactory; switch (selectionSorterOrder) { case ASCENDING: @@ -72,7 +72,7 @@ public boolean equals(Object other) { return true; if (other == null || getClass() != other.getClass()) return false; - SelectionFactorySorter that = (SelectionFactorySorter) other; + FactorySelectionSorter that = (FactorySelectionSorter) other; return Objects.equals(selectionSorterFactory, that.selectionSorterFactory) && Objects.equals(comparator, that.comparator); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 7bc1079bc1..b537bb1d9f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -20,7 +20,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.ValueRangeRecorderId; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; @@ -318,7 +318,7 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach } else if (config.getSorterWeightFactoryClass() != null) { SorterFactory sorterFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new SelectionFactorySorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index a4241c3cd5..8ebc2cb5c0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; @@ -197,7 +197,7 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT } else if (sorterWeightFactoryClass != null) { SorterFactory> sorterFactory = ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); - sorter = new SelectionFactorySorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 74c53bffbf..e37ca61e03 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -19,7 +19,7 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFactorySorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; @@ -338,7 +338,7 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache } else if (config.getSorterWeightFactoryClass() != null) { SorterFactory sorterFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new SelectionFactorySorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(sorterFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java similarity index 89% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java index a37a8160b4..312dd041ae 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFactorySorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java @@ -14,13 +14,13 @@ import org.junit.jupiter.api.Test; -class SelectionFactorySorterTest { +class FactorySelectionSorterTest { @Test void sortAscending() { SorterFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); - SelectionFactorySorter selectionSorter = new SelectionFactorySorter<>( + FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.ASCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); @@ -36,7 +36,7 @@ void sortAscending() { void sortDescending() { SorterFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); - SelectionFactorySorter selectionSorter = new SelectionFactorySorter<>( + FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.DESCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); From 6e95e679a442f9db815346ccb10d9fbb8dceb1f8 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 13 Oct 2025 10:44:45 -0300 Subject: [PATCH 13/33] chore: add CH tests --- .../decorator/FactorySelectionSorter.java | 12 +- ...DefaultConstructionHeuristicPhaseTest.java | 236 ++++++++++++++++-- ...OneValuePerEntityEasyScoreCalculator.java} | 6 +- ...ePerEntityFactoryEasyScoreCalculator.java} | 6 +- ...luePerEntityRangeEasyScoreCalculator.java} | 6 +- ...ntityRangeFactoryEasyScoreCalculator.java} | 6 +- .../OneValuePerEntityEasyScoreCalculator.java | 27 ++ .../comparator/SortableEntityComparator.java | 11 + .../comparator/SortableValueComparator.java | 11 + .../comparator/TestdataSortableEntity.java | 37 +++ .../comparator/TestdataSortableSolution.java | 82 ++++++ .../comparator/TestdataSortableValue.java | 25 ++ ...uePerEntityFactoryEasyScoreCalculator.java | 28 +++ .../sort/factory/SortableEntityFactory.java | 13 + .../sort/factory/SortableValueFactory.java | 13 + .../TestdataFactorySortableEntity.java | 42 ++++ .../TestdataFactorySortableSolution.java | 82 ++++++ .../factory/TestdataFactorySortableValue.java | 29 +++ ...aluePerEntityRangeEasyScoreCalculator.java | 28 +++ .../comparator/SortableEntityComparator.java | 11 + .../comparator/SortableValueComparator.java | 11 + ...TestdataSortableEntityProvidingEntity.java | 53 ++++ ...stdataSortableEntityProvidingSolution.java | 75 ++++++ .../TestdataSortableEntityProvidingValue.java | 25 ++ ...EntityRangeFactoryEasyScoreCalculator.java | 29 +++ .../sort/factory/SortableEntityFactory.java | 15 ++ .../sort/factory/SortableValueFactory.java | 14 ++ ...aFactorySortableEntityProvidingEntity.java | 59 +++++ ...actorySortableEntityProvidingSolution.java | 75 ++++++ ...taFactorySortableEntityProvidingValue.java | 30 +++ 30 files changed, 1061 insertions(+), 36 deletions(-) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/{OneValuePerEntityEasyScoreCalculator.java => ListOneValuePerEntityEasyScoreCalculator.java} (80%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/{OneValuePerEntityFactoryEasyScoreCalculator.java => ListOneValuePerEntityFactoryEasyScoreCalculator.java} (80%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/{OneValuePerEntityRangeEasyScoreCalculator.java => ListOneValuePerEntityRangeEasyScoreCalculator.java} (79%) rename core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/{OneValuePerEntityRangeFactoryEasyScoreCalculator.java => ListOneValuePerEntityRangeFactoryEasyScoreCalculator.java} (78%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index b1ef47fd2c..e5b1f90023 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -24,17 +24,17 @@ public final class FactorySelectionSorter implements SelectionSorter { private final SorterFactory selectionSorterFactory; - private final Comparator comparator; + private final Comparator appliedComparator; public FactorySelectionSorter(SorterFactory selectionSorterFactory, SelectionSorterOrder selectionSorterOrder) { this.selectionSorterFactory = selectionSorterFactory; switch (selectionSorterOrder) { case ASCENDING: - this.comparator = Comparator.naturalOrder(); + this.appliedComparator = Comparator.naturalOrder(); break; case DESCENDING: - this.comparator = Collections.reverseOrder(); + this.appliedComparator = Collections.reverseOrder(); break; default: throw new IllegalStateException("The selectionSorterOrder (" + selectionSorterOrder @@ -53,7 +53,7 @@ public void sort(ScoreDirector scoreDirector, List selectionList) * of {@link PlanningEntity}, planningValue, {@link Move} or {@link Selector} */ public void sort(Solution_ solution, List selectionList) { - SortedMap selectionMap = new TreeMap<>(comparator); + SortedMap selectionMap = new TreeMap<>(appliedComparator); for (T selection : selectionList) { Comparable difficultyWeight = selectionSorterFactory.createSorter(solution, selection); T previous = selectionMap.put(difficultyWeight, selection); @@ -74,11 +74,11 @@ public boolean equals(Object other) { return false; FactorySelectionSorter that = (FactorySelectionSorter) other; return Objects.equals(selectionSorterFactory, that.selectionSorterFactory) - && Objects.equals(comparator, that.comparator); + && Objects.equals(appliedComparator, that.appliedComparator); } @Override public int hashCode() { - return Objects.hash(selectionSorterFactory, comparator); + return Objects.hash(selectionSorterFactory, appliedComparator); } } 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 822f7e27be..dee7183752 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 @@ -16,23 +16,25 @@ import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicForagerConfig; import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicPickEarlyType; +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.list.DestinationSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig; 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.heuristic.selector.value.ValueSorterManner; 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.list.sort.comparator.OneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.sort.comparator.ListOneValuePerEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableEntity; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableSolution; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableValue; -import ai.timefold.solver.core.testdomain.list.sort.factory.OneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.sort.factory.ListOneValuePerEntityFactoryEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableEntity; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableValue; @@ -44,11 +46,11 @@ import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingSolution; import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingValue; -import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.OneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.ListOneValuePerEntityRangeEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingEntity; import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingSolution; import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingValue; -import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.OneValuePerEntityRangeFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.ListOneValuePerEntityRangeFactoryEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingEntity; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingSolution; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingValue; @@ -60,12 +62,24 @@ import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedSolution; +import ai.timefold.solver.core.testdomain.sort.comparator.OneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.TestdataSortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparator.TestdataSortableSolution; +import ai.timefold.solver.core.testdomain.sort.factory.OneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableSolution; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEasyScoreCalculator; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedSolution; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.OneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.OneValuePerEntityRangeFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingSolution; import ai.timefold.solver.core.testutil.PlannerTestUtils; import org.junit.jupiter.api.Test; @@ -349,6 +363,174 @@ void constructionHeuristicAllocateToValueFromQueue() { .filter(e -> e.getValue() == null)).isEmpty(); } + private static List + generateBasicConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { + var values = new ArrayList(); + // Advanced configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from increasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); + return values; + } + + private static List generateBasicConstructionHeuristicConfiguration() { + var values = new ArrayList(); + values.addAll(generateConstructionHeuristicSimpleConfiguration()); + values.addAll(generateBasicConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); + return values; + } + + @ParameterizedTest + @MethodSource("generateBasicConstructionHeuristicConfiguration") + void constructionHeuristicBasicVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataSortableSolution.class, TestdataSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicConstructionHeuristicConfiguration") + void constructionHeuristicBasicVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataFactorySortableSolution.class, TestdataFactorySortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicConstructionHeuristicConfiguration") + void constructionHeuristicVarEntityRangeAllocateToValueFromQueueComparator( + ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataSortableEntityProvidingSolution.class, + TestdataSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicConstructionHeuristicConfiguration") + void constructionHeuristicVarEntityRangeAllocateToValueFromQueueFactory( + ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataFactorySortableEntityProvidingSolution.class, + TestdataFactorySortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataFactorySortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); + } + } + } + private static List generateConstructionHeuristicSimpleConfiguration() { var values = new ArrayList(); values.add(new ConstructionHeuristicTestConfig( @@ -473,11 +655,29 @@ private static List generateConstructionHeurist new int[] { 0, 1, 2 }, // Only the values are sorted, and shuffling the entities will alter the expected result false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.CHEAPEST_INSERTION), + // Since we are starting from increasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 2, 1, 0 }, + // Both are sorted by default + true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_FROM_POOL), + // Since we are starting from increasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 2, 1, 0 }, + // Both are sorted by default + true)); return values; } private static List - generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { + generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { var values = new ArrayList(); // Advanced configuration values.add(new ConstructionHeuristicTestConfig( @@ -583,21 +783,21 @@ private static List generateConstructionHeurist return values; } - private static List generateConstructionHeuristicConfiguration() { + private static List generateListConstructionHeuristicConfiguration() { var values = new ArrayList(); values.addAll(generateConstructionHeuristicSimpleConfiguration()); - values.addAll(generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); + values.addAll(generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); return values; } @ParameterizedTest - @MethodSource("generateConstructionHeuristicConfiguration") + @MethodSource("generateListConstructionHeuristicConfiguration") void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class, TestdataListSortableValue.class) - .withEasyScoreCalculatorClass(OneValuePerEntityEasyScoreCalculator.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityEasyScoreCalculator.class) .withPhases(phaseConfig.config()); var solution = TestdataListSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); @@ -618,13 +818,13 @@ void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHe } @ParameterizedTest - @MethodSource("generateConstructionHeuristicConfiguration") + @MethodSource("generateListConstructionHeuristicConfiguration") void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListFactorySortableSolution.class, TestdataListFactorySortableEntity.class, TestdataListFactorySortableValue.class) - .withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); var solution = TestdataListFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); @@ -644,15 +844,15 @@ void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuri } } - private static List generateEntityRangeConstructionHeuristicConfiguration() { + private static List generateListEntityRangeConstructionHeuristicConfiguration() { var values = new ArrayList(); values.addAll(generateConstructionHeuristicSimpleConfiguration()); - values.addAll(generateConstructionHeuristicAdvancedConfiguration(SelectionCacheType.STEP)); + values.addAll(generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType.STEP)); return values; } @ParameterizedTest - @MethodSource("generateEntityRangeConstructionHeuristicConfiguration") + @MethodSource("generateListEntityRangeConstructionHeuristicConfiguration") void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = @@ -660,7 +860,7 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, TestdataListSortableEntityProvidingEntity.class, TestdataListSortableEntityProvidingValue.class) - .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); var solution = TestdataListSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); @@ -681,14 +881,14 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( } @ParameterizedTest - @MethodSource("generateEntityRangeConstructionHeuristicConfiguration") + @MethodSource("generateListEntityRangeConstructionHeuristicConfiguration") void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListFactorySortableEntityProvidingSolution.class, TestdataListFactorySortableEntityProvidingEntity.class, TestdataListFactorySortableEntityProvidingValue.class) - .withEasyScoreCalculatorClass(OneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); var solution = TestdataListFactorySortableEntityProvidingSolution.generateSolution(3, 3, @@ -716,7 +916,7 @@ void failConstructionHeuristicEntityRange() { .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, TestdataListSortableEntityProvidingEntity.class, TestdataListSortableEntityProvidingValue.class) - .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeEasyScoreCalculator.class) .withPhases( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListOneValuePerEntityEasyScoreCalculator.java similarity index 80% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListOneValuePerEntityEasyScoreCalculator.java index c720265857..fc9034b656 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/OneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListOneValuePerEntityEasyScoreCalculator.java @@ -5,14 +5,14 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityEasyScoreCalculator +public class ListOneValuePerEntityEasyScoreCalculator implements EasyScoreCalculator { @Override - public @NonNull HardSoftScore calculateScore(@NonNull TestdataListSortableSolution testdataListSortableSolution) { + public @NonNull HardSoftScore calculateScore(@NonNull TestdataListSortableSolution solution) { var softScore = 0; var hardScore = 0; - for (var entity : testdataListSortableSolution.getEntityList()) { + for (var entity : solution.getEntityList()) { if (entity.getValueList().size() == 1) { softScore -= 10; } else { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListOneValuePerEntityFactoryEasyScoreCalculator.java similarity index 80% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListOneValuePerEntityFactoryEasyScoreCalculator.java index 8ff8b97180..895efffbff 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListOneValuePerEntityFactoryEasyScoreCalculator.java @@ -5,15 +5,15 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityFactoryEasyScoreCalculator +public class ListOneValuePerEntityFactoryEasyScoreCalculator implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataListFactorySortableSolution testdataListFactorySortableSolution) { + calculateScore(@NonNull TestdataListFactorySortableSolution solution) { var softScore = 0; var hardScore = 0; - for (var entity : testdataListFactorySortableSolution.getEntityList()) { + for (var entity : solution.getEntityList()) { if (entity.getValueList().size() == 1) { softScore -= 10; } else { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListOneValuePerEntityRangeEasyScoreCalculator.java similarity index 79% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListOneValuePerEntityRangeEasyScoreCalculator.java index 76c238fc92..8f7cbe807d 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListOneValuePerEntityRangeEasyScoreCalculator.java @@ -5,15 +5,15 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityRangeEasyScoreCalculator +public class ListOneValuePerEntityRangeEasyScoreCalculator implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataListSortableEntityProvidingSolution testdataListSortableEntityProvidingSolution) { + calculateScore(@NonNull TestdataListSortableEntityProvidingSolution solution) { var softScore = 0; var hardScore = 0; - for (var entity : testdataListSortableEntityProvidingSolution.getEntityList()) { + for (var entity : solution.getEntityList()) { if (entity.getValueList().size() == 1) { softScore -= 10; } else { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListOneValuePerEntityRangeFactoryEasyScoreCalculator.java similarity index 78% rename from core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListOneValuePerEntityRangeFactoryEasyScoreCalculator.java index 797baf833f..c70072e040 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListOneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -5,16 +5,16 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityRangeFactoryEasyScoreCalculator +public class ListOneValuePerEntityRangeFactoryEasyScoreCalculator implements EasyScoreCalculator { @Override public @NonNull HardSoftScore calculateScore( - @NonNull TestdataListFactorySortableEntityProvidingSolution testdataListFactorySortableEntityProvidingSolution) { + @NonNull TestdataListFactorySortableEntityProvidingSolution solution) { var softScore = 0; var hardScore = 0; - for (var entity : testdataListFactorySortableEntityProvidingSolution.getEntityList()) { + for (var entity : solution.getEntityList()) { if (entity.getValueList().size() == 1) { softScore -= 10; } else { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java new file mode 100644 index 0000000000..5339ac2c39 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java @@ -0,0 +1,27 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore calculateScore(@NonNull TestdataSortableSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataSortableEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataSortableEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java new file mode 100644 index 0000000000..f2bf58c598 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import java.util.Comparator; + +public class SortableEntityComparator implements Comparator { + + @Override + public int compare(TestdataSortableEntity e1, TestdataSortableEntity e2) { + return e1.getDifficulty() - e2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java new file mode 100644 index 0000000000..98c91368f6 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import java.util.Comparator; + +public class SortableValueComparator implements Comparator { + + @Override + public int compare(TestdataSortableValue v1, TestdataSortableValue v2) { + return v1.getStrength() - v2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java new file mode 100644 index 0000000000..07c605dd91 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +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(difficultyComparatorClass = SortableEntityComparator.class) +public class TestdataSortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = SortableValueComparator.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataSortableEntity() { + } + + public TestdataSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/comparator/TestdataSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java new file mode 100644 index 0000000000..bdf7b2db38 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java @@ -0,0 +1,82 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataSortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataSortableSolution.class, + TestdataSortableEntity.class, + TestdataSortableValue.class); + } + + public static TestdataSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataSortableEntity("Generated Entity " + i, i)) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } + TestdataSortableSolution solution = new TestdataSortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @ProblemFactCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java new file mode 100644 index 0000000000..ef6b2d49a6 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java @@ -0,0 +1,25 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataSortableValue extends TestdataObject { + + private int strength; + + public TestdataSortableValue() { + } + + public TestdataSortableValue(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/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..19d08a52e9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataFactorySortableSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataFactorySortableEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataFactorySortableEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java new file mode 100644 index 0000000000..52e8244b0b --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterFactory; + +public class SortableEntityFactory + implements SorterFactory { + + @Override + public Comparable createSorter(TestdataFactorySortableSolution solution, + TestdataFactorySortableEntity selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java new file mode 100644 index 0000000000..b643ae7a47 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; + +public class SortableValueFactory + implements SelectionSorterWeightFactory { + + @Override + public Comparable createSorterWeight(TestdataFactorySortableSolution solution, TestdataFactorySortableValue selection) { + return selection; + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java new file mode 100644 index 0000000000..a6d03977c8 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java @@ -0,0 +1,42 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +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(difficultyWeightFactoryClass = SortableEntityFactory.class) +public class TestdataFactorySortableEntity extends TestdataObject implements Comparable { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = SortableValueFactory.class) + private TestdataFactorySortableValue value; + private int difficulty; + + public TestdataFactorySortableEntity() { + } + + public TestdataFactorySortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataFactorySortableValue getValue() { + return value; + } + + public void setValue(TestdataFactorySortableValue value) { + this.value = value; + } + + public int getDifficulty() { + return difficulty; + } + + public void setDifficulty(int difficulty) { + this.difficulty = difficulty; + } + + @Override + public int compareTo(TestdataFactorySortableEntity o) { + return difficulty - o.difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java new file mode 100644 index 0000000000..4d899f9f99 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java @@ -0,0 +1,82 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataFactorySortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataFactorySortableSolution.class, + TestdataFactorySortableEntity.class, + TestdataFactorySortableValue.class); + } + + public static TestdataFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataFactorySortableEntity("Generated Entity " + i, i)) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataFactorySortableValue("Generated Value " + i, i)) + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } + TestdataFactorySortableSolution solution = new TestdataFactorySortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @ProblemFactCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataFactorySortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java new file mode 100644 index 0000000000..1564e55c2a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java @@ -0,0 +1,29 @@ +package ai.timefold.solver.core.testdomain.sort.factory; + +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataFactorySortableValue extends TestdataObject implements Comparable { + + private int strength; + + public TestdataFactorySortableValue() { + } + + public TestdataFactorySortableValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + @Override + public int compareTo(TestdataFactorySortableValue o) { + return strength - o.strength; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java new file mode 100644 index 0000000000..7bca2b3d45 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityRangeEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataSortableEntityProvidingSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java new file mode 100644 index 0000000000..1ee219fc72 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import java.util.Comparator; + +public class SortableEntityComparator implements Comparator { + + @Override + public int compare(TestdataSortableEntityProvidingEntity e1, TestdataSortableEntityProvidingEntity e2) { + return e1.getDifficulty() - e2.getDifficulty(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java new file mode 100644 index 0000000000..bf2b7f6fb6 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import java.util.Comparator; + +public class SortableValueComparator implements Comparator { + + @Override + public int compare(TestdataSortableEntityProvidingValue v1, TestdataSortableEntityProvidingValue v2) { + return v1.getStrength() - v2.getStrength(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..e00e531ee2 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java @@ -0,0 +1,53 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyComparatorClass = SortableEntityComparator.class) +public class TestdataSortableEntityProvidingEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = SortableValueComparator.class) + private TestdataSortableEntityProvidingValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataSortableEntityProvidingEntity() { + } + + public TestdataSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableEntityProvidingValue getValue() { + return value; + } + + public void setValue(TestdataSortableEntityProvidingValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + 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/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java new file mode 100644 index 0000000000..aa52f438c9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java @@ -0,0 +1,75 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataSortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataSortableEntityProvidingSolution.class, + TestdataSortableEntityProvidingEntity.class, + TestdataSortableEntityProvidingValue.class); + } + + public static TestdataSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataSortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList()); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableEntityProvidingValue("Generated Value " + i, i)) + .toList(); + var random = new Random(0); + var solution = new TestdataSortableEntityProvidingSolution(); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + if (shuffle) { + Collections.shuffle(valueRange, random); + } + entity.setValueRange(valueRange); + } + if (shuffle) { + Collections.shuffle(entityList, random); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private HardSoftScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataSortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java new file mode 100644 index 0000000000..69b694db38 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java @@ -0,0 +1,25 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; + +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataSortableEntityProvidingValue extends TestdataObject { + + private int strength; + + public TestdataSortableEntityProvidingValue() { + } + + public TestdataSortableEntityProvidingValue(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/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..f121259d6e --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -0,0 +1,29 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OneValuePerEntityRangeFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore( + @NonNull TestdataFactorySortableEntityProvidingSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataFactorySortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataFactorySortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java new file mode 100644 index 0000000000..4f98918326 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import ai.timefold.solver.core.api.domain.common.SorterFactory; + +public class SortableEntityFactory + implements + SorterFactory { + + @Override + public Comparable createSorter( + TestdataFactorySortableEntityProvidingSolution solution, + TestdataFactorySortableEntityProvidingEntity selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java new file mode 100644 index 0000000000..17f4bf3ea5 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; + +public class SortableValueFactory + implements + SelectionSorterWeightFactory { + + @Override + public Comparable createSorterWeight(TestdataFactorySortableEntityProvidingSolution solution, + TestdataFactorySortableEntityProvidingValue selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java new file mode 100644 index 0000000000..76d606c543 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java @@ -0,0 +1,59 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; + +@PlanningEntity(difficultyWeightFactoryClass = SortableEntityFactory.class) +public class TestdataFactorySortableEntityProvidingEntity extends TestdataObject + implements Comparable { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = SortableValueFactory.class) + private TestdataFactorySortableEntityProvidingValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataFactorySortableEntityProvidingEntity() { + } + + public TestdataFactorySortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataFactorySortableEntityProvidingValue getValue() { + return value; + } + + public void setValue(TestdataFactorySortableEntityProvidingValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + public int getDifficulty() { + return difficulty; + } + + public void setDifficulty(int difficulty) { + this.difficulty = difficulty; + } + + @Override + public int compareTo(TestdataFactorySortableEntityProvidingEntity o) { + return difficulty - o.difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java new file mode 100644 index 0000000000..ee3bae5bd9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java @@ -0,0 +1,75 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; + +@PlanningSolution +public class TestdataFactorySortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataFactorySortableEntityProvidingSolution.class, + TestdataFactorySortableEntityProvidingEntity.class, + TestdataFactorySortableEntityProvidingValue.class); + } + + public static TestdataFactorySortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList()); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataFactorySortableEntityProvidingValue("Generated Value " + i, i)) + .toList(); + var solution = new TestdataFactorySortableEntityProvidingSolution(); + var random = new Random(0); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + if (shuffle) { + Collections.shuffle(valueRange, random); + } + entity.setValueRange(valueRange); + } + if (shuffle) { + Collections.shuffle(entityList, random); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private HardSoftScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataFactorySortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java new file mode 100644 index 0000000000..45705e6431 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java @@ -0,0 +1,30 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; + +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataFactorySortableEntityProvidingValue extends TestdataObject + implements Comparable { + + private int strength; + + public TestdataFactorySortableEntityProvidingValue() { + } + + public TestdataFactorySortableEntityProvidingValue(String code, int strength) { + super(code); + this.strength = strength; + } + + public int getStrength() { + return strength; + } + + public void setStrength(int strength) { + this.strength = strength; + } + + @Override + public int compareTo(TestdataFactorySortableEntityProvidingValue o) { + return strength - o.strength; + } +} From 07dc956dcaee6210baf122927e6028bdff4a45c3 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 13 Oct 2025 15:44:40 -0300 Subject: [PATCH 14/33] chore: add new basic var sorting setting --- .../api/domain/variable/PlanningVariable.java | 32 + .../descriptor/BasicVariableDescriptor.java | 64 +- .../decorator/FactorySelectionSorter.java | 2 +- .../SelectionSorterWeightFactory.java | 2 +- ...DefaultConstructionHeuristicPhaseTest.java | 644 +++++++++++------- .../DummyHardSoftEasyScoreCalculator.java | 14 + .../common/DummyValueComparator.java | 13 + .../testdomain/common/DummyValueFactory.java | 14 + .../common/DummyWeightValueFactory.java | 14 + .../common/TestSortableComparator.java | 11 + .../common/TestSortableFactory.java | 17 + .../testdomain/common/TestSortableObject.java | 11 + .../TestdataSortableValue.java | 12 +- .../ListSortableEntityComparator.java | 11 - .../ListSortableValueComparator.java | 11 - .../TestdataListSortableEntity.java | 22 +- .../TestdataListSortableSolution.java | 14 +- .../comparator/TestdataListSortableValue.java | 38 -- .../factory/ListSortableEntityFactory.java | 13 - .../factory/ListSortableValueFactory.java | 13 - .../TestdataListFactorySortableEntity.java | 27 +- .../TestdataListFactorySortableSolution.java | 14 +- .../TestdataListFactorySortableValue.java | 43 -- .../TestdataInvalidListSortableEntity.java | 45 ++ .../TestdataInvalidListSortableSolution.java | 52 ++ .../ListSortableEntityComparator.java | 11 - .../ListSortableValueComparator.java | 11 - ...dataListSortableEntityProvidingEntity.java | 28 +- ...taListSortableEntityProvidingSolution.java | 5 +- ...tdataListSortableEntityProvidingValue.java | 38 -- .../factory/ListSortableEntityFactory.java | 15 - .../factory/ListSortableValueFactory.java | 15 - ...tFactorySortableEntityProvidingEntity.java | 33 +- ...actorySortableEntityProvidingSolution.java | 5 +- ...stFactorySortableEntityProvidingValue.java | 44 -- .../comparator/SortableEntityComparator.java | 11 - .../comparator/SortableValueComparator.java | 11 - ...wOneValuePerEntityEasyScoreCalculator.java | 29 + .../TestdataNewSortableEntity.java | 37 + .../TestdataNewSortableSolution.java} | 25 +- ...OneValuePerEntityEasyScoreCalculator.java} | 12 +- .../TestdataOldSortableEntity.java | 37 + .../TestdataOldSortableSolution.java} | 35 +- .../sort/factory/SortableEntityFactory.java | 13 - .../sort/factory/SortableValueFactory.java | 13 - .../TestdataFactorySortableEntity.java | 42 -- .../factory/TestdataFactorySortableValue.java | 29 - ...ePerEntityFactoryEasyScoreCalculator.java} | 12 +- .../TestdataFactoryNewSortableEntity.java | 37 + .../TestdataFactoryNewSortableSolution.java | 83 +++ ...uePerEntityFactoryEasyScoreCalculator.java | 28 + .../TestdataFactoryOldSortableEntity.java | 37 + .../TestdataFactoryOldSortableSolution.java | 83 +++ ...aInvalidMixedComparatorSortableEntity.java | 41 ++ ...nvalidMixedComparatorSortableSolution.java | 52 ++ ...taInvalidMixedStrengthSortableEntity.java} | 16 +- ...aInvalidMixedStrengthSortableSolution.java | 52 ++ ...ataInvalidTwoComparatorSortableEntity.java | 40 ++ ...aInvalidTwoComparatorSortableSolution.java | 52 ++ ...stdataInvalidTwoFactorySortableEntity.java | 41 ++ ...dataInvalidTwoFactorySortableSolution.java | 52 ++ .../comparator/SortableEntityComparator.java | 11 - .../comparator/SortableValueComparator.java | 11 - ...TestdataSortableEntityProvidingEntity.java | 53 -- .../TestdataSortableEntityProvidingValue.java | 25 - ...luePerEntityRangeEasyScoreCalculator.java} | 12 +- ...tdataNewSortableEntityProvidingEntity.java | 53 ++ ...taNewSortableEntityProvidingSolution.java} | 30 +- ...aluePerEntityRangeEasyScoreCalculator.java | 28 + ...tdataOldSortableEntityProvidingEntity.java | 53 ++ ...ataOldSortableEntityProvidingSolution.java | 75 ++ .../sort/factory/SortableEntityFactory.java | 15 - .../sort/factory/SortableValueFactory.java | 14 - ...aFactorySortableEntityProvidingEntity.java | 59 -- ...taFactorySortableEntityProvidingValue.java | 30 - ...ntityRangeFactoryEasyScoreCalculator.java} | 12 +- ...ctoryNewSortableEntityProvidingEntity.java | 54 ++ ...ryNewSortableEntityProvidingSolution.java} | 30 +- ...EntityRangeFactoryEasyScoreCalculator.java | 29 + ...ctoryOldSortableEntityProvidingEntity.java | 54 ++ ...oryOldSortableEntityProvidingSolution.java | 75 ++ 81 files changed, 1993 insertions(+), 1008 deletions(-) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyHardSoftEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObject.java rename core/src/test/java/ai/timefold/solver/core/testdomain/{sort/comparator => common}/TestdataSortableValue.java (54%) delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableSolution.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/{TestdataSortableSolution.java => newapproach/TestdataNewSortableSolution.java} (70%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/{OneValuePerEntityEasyScoreCalculator.java => oldapproach/OldOneValuePerEntityEasyScoreCalculator.java} (65%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{factory/TestdataFactorySortableSolution.java => comparator/oldapproach/TestdataOldSortableSolution.java} (60%) delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/{OneValuePerEntityFactoryEasyScoreCalculator.java => newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java} (60%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableSolution.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{comparator/TestdataSortableEntity.java => invalid/mixed/strength/TestdataInvalidMixedStrengthSortableEntity.java} (52%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/{OneValuePerEntityRangeEasyScoreCalculator.java => newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java} (63%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/{TestdataSortableEntityProvidingSolution.java => newapproach/TestdataNewSortableEntityProvidingSolution.java} (60%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/{OneValuePerEntityRangeFactoryEasyScoreCalculator.java => newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java} (63%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/{TestdataFactorySortableEntityProvidingSolution.java => newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java} (58%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 5af5055ab6..f3c5653a2e 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -8,6 +8,7 @@ import java.lang.annotation.Target; import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; @@ -78,11 +79,29 @@ *

* Do not use together with {@link #strengthWeightFactoryClass()}. * + * @deprecated Deprecated in favor of {@link #comparatorClass()}. + * * @return {@link NullStrengthComparator} when it is null (workaround for annotation limitation) * @see #strengthWeightFactoryClass() */ + @Deprecated(forRemoval = true, since = "1.28.0") Class strengthComparatorClass() default NullStrengthComparator.class; + /** + * Allows sorting a collection of planning values for this variable. + * Some algorithms perform better when the values are sorted based on specific metrics. + *

+ * The {@link Comparator} should sort the data in ascending order. + * For example, prioritize three visits by sorting them based on their importance: + * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY) + *

+ * Do not use together with {@link #comparatorFactoryClass()}. + * + * @return {@link NullComparator} when it is null (workaround for annotation limitation) + * @see #comparatorFactoryClass() + */ + Class comparatorClass() default NullComparator.class; + /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ interface NullStrengthComparator extends NullComparator { } @@ -95,11 +114,24 @@ interface NullComparator extends Comparator { *

* Do not use together with {@link #strengthComparatorClass()}. * + * @deprecated Deprecated in favor of {@link #comparatorFactoryClass()}. + * * @return {@link NullStrengthWeightFactory} when it is null (workaround for annotation limitation) * @see #strengthComparatorClass() */ + @Deprecated(forRemoval = true, since = "1.28.0") Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; + /** + * The {@link SorterFactory} alternative for {@link #comparatorClass()}. + *

+ * Do not use together with {@link #comparatorClass()}. + * + * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) + * @see #comparatorClass() + */ + Class comparatorFactoryClass() default NullComparatorFactory.class; + /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ interface NullStrengthWeightFactory extends NullComparatorFactory { } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java index 18abcb3892..86af0a31fe 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java @@ -1,5 +1,8 @@ package ai.timefold.solver.core.impl.domain.variable.descriptor; +import java.util.Comparator; + +import ai.timefold.solver.core.api.domain.common.SorterFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; @@ -41,8 +44,60 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) { processAllowsUnassigned(planningVariableAnnotation); processChained(planningVariableAnnotation); processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs()); - processSorting("strengthComparatorClass", planningVariableAnnotation.strengthComparatorClass(), - "strengthWeightFactoryClass", planningVariableAnnotation.strengthWeightFactoryClass()); + var sortingProperties = assertSortingProperties(planningVariableAnnotation); + processSorting(sortingProperties.comparatorPropertyName(), sortingProperties.comparatorClass(), + sortingProperties.comparatorFactoryPropertyName(), sortingProperties.comparatorFactoryClass()); + } + + private SortingProperties assertSortingProperties(PlanningVariable planningVariableAnnotation) { + // Comparator property + var strengthComparatorClass = planningVariableAnnotation.strengthComparatorClass(); + var comparatorClass = planningVariableAnnotation.comparatorClass(); + if (strengthComparatorClass != null + && PlanningVariable.NullComparator.class.isAssignableFrom(strengthComparatorClass)) { + strengthComparatorClass = null; + } + if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) { + comparatorClass = null; + } + if (strengthComparatorClass != null && comparatorClass != null) { + throw new IllegalStateException( + "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted( + entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthComparatorClass", + strengthComparatorClass.getName(), "comparatorClass", comparatorClass.getName())); + } + // Comparator factory property + var strengthWeightFactoryClass = planningVariableAnnotation.strengthWeightFactoryClass(); + var comparatorFactoryClass = planningVariableAnnotation.comparatorFactoryClass(); + if (strengthWeightFactoryClass != null + && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(strengthWeightFactoryClass)) { + strengthWeightFactoryClass = null; + } + if (comparatorFactoryClass != null + && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) { + comparatorFactoryClass = null; + } + if (strengthWeightFactoryClass != null && comparatorFactoryClass != null) { + throw new IllegalStateException( + "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted( + entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthWeightFactoryClass", + strengthWeightFactoryClass.getName(), "comparatorFactoryClass", comparatorFactoryClass.getName())); + } + // Final properties + var comparatorPropertyName = "comparatorClass"; + var comparatorPropertyClass = comparatorClass; + var factoryPropertyName = "comparatorFactoryClass"; + var factoryPropertyClass = comparatorFactoryClass; + if (strengthComparatorClass != null) { + comparatorPropertyName = "strengthComparatorClass"; + comparatorPropertyClass = strengthComparatorClass; + } + if (strengthWeightFactoryClass != null) { + factoryPropertyName = "strengthWeightFactoryClass"; + factoryPropertyClass = strengthWeightFactoryClass; + } + return new SortingProperties(comparatorPropertyName, comparatorPropertyClass, factoryPropertyName, + factoryPropertyClass); } private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) { @@ -130,4 +185,9 @@ public SelectionFilter getMovableChainedTrailingValueFilter() return movableChainedTrailingValueFilter; } + private record SortingProperties(String comparatorPropertyName, Class comparatorClass, + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index e5b1f90023..db03a7b737 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -27,7 +27,7 @@ public final class FactorySelectionSorter implements SelectionSort private final Comparator appliedComparator; public FactorySelectionSorter(SorterFactory selectionSorterFactory, - SelectionSorterOrder selectionSorterOrder) { + SelectionSorterOrder selectionSorterOrder) { this.selectionSorterFactory = selectionSorterFactory; switch (selectionSorterOrder) { case ASCENDING: diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 13d40923ef..fb66b6a606 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -21,7 +21,7 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ -@Deprecated(forRemoval = true, since = "1.27.0") +@Deprecated(forRemoval = true, since = "1.28.0") public interface SelectionSorterWeightFactory extends SorterFactory { Comparable createSorterWeight(Solution_ solution, T selection); 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 dee7183752..9f6221d05d 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 @@ -30,14 +30,15 @@ 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.common.DummyHardSoftEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.comparator.ListOneValuePerEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableEntity; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableSolution; -import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableValue; import ai.timefold.solver.core.testdomain.list.sort.factory.ListOneValuePerEntityFactoryEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableEntity; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; -import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableValue; +import ai.timefold.solver.core.testdomain.list.sort.invalid.TestdataInvalidListSortableEntity; +import ai.timefold.solver.core.testdomain.list.sort.invalid.TestdataInvalidListSortableSolution; 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; @@ -49,11 +50,9 @@ import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.ListOneValuePerEntityRangeEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingEntity; import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator.TestdataListSortableEntityProvidingValue; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.ListOneValuePerEntityRangeFactoryEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingEntity; import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.list.valuerange.sort.factory.TestdataListFactorySortableEntityProvidingValue; 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; @@ -62,24 +61,44 @@ import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedSolution; -import ai.timefold.solver.core.testdomain.sort.comparator.OneValuePerEntityEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.comparator.TestdataSortableEntity; -import ai.timefold.solver.core.testdomain.sort.comparator.TestdataSortableSolution; -import ai.timefold.solver.core.testdomain.sort.factory.OneValuePerEntityFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableEntity; -import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableSolution; +import ai.timefold.solver.core.testdomain.sort.comparator.NewOneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableSolution; +import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.OldOneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.TestdataOldSortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.TestdataOldSortableSolution; +import ai.timefold.solver.core.testdomain.sort.factory.newapproach.NewOneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.factory.newapproach.TestdataFactoryNewSortableEntity; +import ai.timefold.solver.core.testdomain.sort.factory.newapproach.TestdataFactoryNewSortableSolution; +import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.OldOneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.TestdataFactoryOldSortableEntity; +import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.TestdataFactoryOldSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableSolution; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEasyScoreCalculator; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedSolution; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.OneValuePerEntityRangeEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.OneValuePerEntityRangeFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.NewOneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.TestdataNewSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.TestdataNewSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.OldOneValuePerEntityRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.TestdataOldSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.TestdataOldSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.NewOneValuePerEntityRangeFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.TestdataFactoryNewSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.TestdataFactoryNewSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.OldOneValuePerEntityRangeFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.TestdataFactoryOldSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.TestdataFactoryOldSortableEntityProvidingSolution; import ai.timefold.solver.core.testutil.PlannerTestUtils; import org.junit.jupiter.api.Test; @@ -347,191 +366,7 @@ void solveWithEntityValueRangeListVariable() { .hasSameElementsAs(List.of("v3")); } - @Test - void constructionHeuristicAllocateToValueFromQueue() { - var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) - .withPhases(new ConstructionHeuristicPhaseConfig() - .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE)); - - var solution = new TestdataSolution("s1"); - solution.setValueList(Arrays.asList(new TestdataValue("v1"), new TestdataValue("v2"))); - solution.setEntityList(Arrays.asList(new TestdataEntity("e1"))); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - assertThat(solution.getEntityList().stream() - .filter(e -> e.getValue() == null)).isEmpty(); - } - - private static List - generateBasicConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { - var values = new ArrayList(); - // Advanced configuration - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfig(new QueuedEntityPlacerConfig() - .withEntitySelectorConfig(new EntitySelectorConfig() - .withId("sortedEntitySelector") - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) - .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() - .withEntitySelectorConfig( - new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) - .withValueSelectorConfig( - new ValueSelectorConfig() - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(entityDestinationCacheType) - .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)))) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // Since we are starting from decreasing strength - // and the entities are being read in decreasing order of difficulty, - // this is expected: e1[1], e2[2], and e3[3] - new int[] { 0, 1, 2 }, - // Both are sorted and the expected result won't be affected - true)); - values.add(new ConstructionHeuristicTestConfig( - new ConstructionHeuristicPhaseConfig() - .withEntityPlacerConfig(new QueuedEntityPlacerConfig() - .withEntitySelectorConfig(new EntitySelectorConfig() - .withId("sortedEntitySelector") - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(SelectionCacheType.PHASE) - .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) - .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() - .withEntitySelectorConfig( - new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) - .withValueSelectorConfig( - new ValueSelectorConfig() - .withSelectionOrder(SelectionOrder.SORTED) - .withCacheType(entityDestinationCacheType) - .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)))) - .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( - ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), - // Since we are starting from increasing strength - // and the entities are being read in decreasing order of difficulty, - // this is expected: e1[3], e2[2], and e3[1] - new int[] { 2, 1, 0 }, - // Both are sorted and the expected result won't be affected - true)); - return values; - } - - private static List generateBasicConstructionHeuristicConfiguration() { - var values = new ArrayList(); - values.addAll(generateConstructionHeuristicSimpleConfiguration()); - values.addAll(generateBasicConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); - return values; - } - - @ParameterizedTest - @MethodSource("generateBasicConstructionHeuristicConfiguration") - void constructionHeuristicBasicVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { - var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataSortableSolution.class, TestdataSortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityEasyScoreCalculator.class); - solverConfig.withPhases(phaseConfig.config()); - - var solution = TestdataSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - if (phaseConfig.expected() != null) { - for (var i = 0; i < 3; i++) { - var id = "Generated Entity %d".formatted(i); - var entity = solution.getEntityList().stream() - .filter(e -> e.getCode().equals(id)) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - assertThat(entity.getValue()).isNotNull(); - assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); - } - } - } - - @ParameterizedTest - @MethodSource("generateBasicConstructionHeuristicConfiguration") - void constructionHeuristicBasicVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { - var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataFactorySortableSolution.class, TestdataFactorySortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class); - solverConfig.withPhases(phaseConfig.config()); - - var solution = TestdataFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - if (phaseConfig.expected() != null) { - for (var i = 0; i < 3; i++) { - var id = "Generated Entity %d".formatted(i); - var entity = solution.getEntityList().stream() - .filter(e -> e.getCode().equals(id)) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - assertThat(entity.getValue()).isNotNull(); - assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); - } - } - } - - @ParameterizedTest - @MethodSource("generateBasicConstructionHeuristicConfiguration") - void constructionHeuristicVarEntityRangeAllocateToValueFromQueueComparator( - ConstructionHeuristicTestConfig phaseConfig) { - var solverConfig = - PlannerTestUtils - .buildSolverConfig(TestdataSortableEntityProvidingSolution.class, - TestdataSortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(OneValuePerEntityRangeEasyScoreCalculator.class) - .withPhases(phaseConfig.config()); - - var solution = TestdataSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - if (phaseConfig.expected() != null) { - for (var i = 0; i < 3; i++) { - var id = "Generated Entity %d".formatted(i); - var entity = solution.getEntityList().stream() - .filter(e -> e.getCode().equals(id)) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - assertThat(entity.getValue()).isNotNull(); - assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); - } - } - } - - @ParameterizedTest - @MethodSource("generateBasicConstructionHeuristicConfiguration") - void constructionHeuristicVarEntityRangeAllocateToValueFromQueueFactory( - ConstructionHeuristicTestConfig phaseConfig) { - var solverConfig = - PlannerTestUtils - .buildSolverConfig(TestdataFactorySortableEntityProvidingSolution.class, - TestdataFactorySortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(OneValuePerEntityRangeFactoryEasyScoreCalculator.class) - .withPhases(phaseConfig.config()); - - var solution = TestdataFactorySortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); - - solution = PlannerTestUtils.solve(solverConfig, solution); - assertThat(solution).isNotNull(); - if (phaseConfig.expected() != null) { - for (var i = 0; i < 3; i++) { - var id = "Generated Entity %d".formatted(i); - var entity = solution.getEntityList().stream() - .filter(e -> e.getCode().equals(id)) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - assertThat(entity.getValue()).isNotNull(); - assertThat(entity.getValue().getStrength()).isEqualTo(phaseConfig.expected[i]); - } - } - } - - private static List generateConstructionHeuristicSimpleConfiguration() { + private static List generateCommonConfiguration() { var values = new ArrayList(); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() @@ -677,7 +512,277 @@ private static List generateConstructionHeurist } private static List - generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType entityDestinationCacheType) { + generateAdvancedBasicVariableConfiguration(SelectionCacheType entityDestinationCacheType) { + var values = new ArrayList(); + // Advanced configuration + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.DECREASING_STRENGTH)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DECREASING_DIFFICULTY)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.INCREASING_STRENGTH)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from increasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); + return values; + } + + private static List generateBasicVariableConfiguration() { + var values = new ArrayList(); + values.addAll(generateCommonConfiguration()); + values.addAll(generateAdvancedBasicVariableConfiguration(SelectionCacheType.PHASE)); + return values; + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveOldBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataOldSortableSolution.class, TestdataOldSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OldOneValuePerEntityEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataOldSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveOldBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataFactoryOldSortableSolution.class, TestdataFactoryOldSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OldOneValuePerEntityFactoryEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataFactoryOldSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveOldBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataOldSortableEntityProvidingSolution.class, + TestdataOldSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OldOneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataOldSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveOldBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataFactoryOldSortableEntityProvidingSolution.class, + TestdataFactoryOldSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OldOneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataFactoryOldSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveNewBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataNewSortableSolution.class, TestdataNewSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(NewOneValuePerEntityEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataNewSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveNewBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = PlannerTestUtils + .buildSolverConfig(TestdataFactoryNewSortableSolution.class, TestdataFactoryNewSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(NewOneValuePerEntityFactoryEasyScoreCalculator.class); + solverConfig.withPhases(phaseConfig.config()); + + var solution = TestdataFactoryNewSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveNewBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataNewSortableEntityProvidingSolution.class, + TestdataNewSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(NewOneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataNewSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + @ParameterizedTest + @MethodSource("generateBasicVariableConfiguration") + void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataFactoryNewSortableEntityProvidingSolution.class, + TestdataFactoryNewSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(NewOneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataFactoryNewSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(entity.getValue().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + } + } + } + + private static List + generateAdvancedListVariableConfiguration(SelectionCacheType entityDestinationCacheType) { var values = new ArrayList(); // Advanced configuration values.add(new ConstructionHeuristicTestConfig( @@ -783,20 +888,19 @@ private static List generateConstructionHeurist return values; } - private static List generateListConstructionHeuristicConfiguration() { + private static List generateListVariableConfiguration() { var values = new ArrayList(); - values.addAll(generateConstructionHeuristicSimpleConfiguration()); - values.addAll(generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType.PHASE)); + values.addAll(generateCommonConfiguration()); + values.addAll(generateAdvancedListVariableConfiguration(SelectionCacheType.PHASE)); return values; } @ParameterizedTest - @MethodSource("generateListConstructionHeuristicConfiguration") - void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + @MethodSource("generateListVariableConfiguration") + void solveListVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class, - TestdataListSortableValue.class) + .buildSolverConfig(TestdataListSortableSolution.class, TestdataListSortableEntity.class) .withEasyScoreCalculatorClass(ListOneValuePerEntityEasyScoreCalculator.class) .withPhases(phaseConfig.config()); @@ -812,18 +916,17 @@ void constructionHeuristicListVarAllocateValueFromQueueComparator(ConstructionHe .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @ParameterizedTest - @MethodSource("generateListConstructionHeuristicConfiguration") - void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + @MethodSource("generateListVariableConfiguration") + void solveListVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataListFactorySortableSolution.class, TestdataListFactorySortableEntity.class, - TestdataListFactorySortableValue.class) + .buildSolverConfig(TestdataListFactorySortableSolution.class, TestdataListFactorySortableEntity.class) .withEasyScoreCalculatorClass(ListOneValuePerEntityFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); @@ -839,27 +942,25 @@ void constructionHeuristicListVarAllocateValueFromQueueFactory(ConstructionHeuri .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } - private static List generateListEntityRangeConstructionHeuristicConfiguration() { + private static List generateListVariableEntityRangeConfiguration() { var values = new ArrayList(); - values.addAll(generateConstructionHeuristicSimpleConfiguration()); - values.addAll(generateListConstructionHeuristicAdvancedConfiguration(SelectionCacheType.STEP)); + values.addAll(generateCommonConfiguration()); + values.addAll(generateAdvancedListVariableConfiguration(SelectionCacheType.STEP)); return values; } @ParameterizedTest - @MethodSource("generateListEntityRangeConstructionHeuristicConfiguration") - void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( - ConstructionHeuristicTestConfig phaseConfig) { + @MethodSource("generateListVariableEntityRangeConfiguration") + void solveListVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, - TestdataListSortableEntityProvidingEntity.class, - TestdataListSortableEntityProvidingValue.class) + TestdataListSortableEntityProvidingEntity.class) .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); @@ -875,19 +976,18 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueComparator( .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @ParameterizedTest - @MethodSource("generateListEntityRangeConstructionHeuristicConfiguration") - void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + @MethodSource("generateListVariableEntityRangeConfiguration") + void solveListVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListFactorySortableEntityProvidingSolution.class, - TestdataListFactorySortableEntityProvidingEntity.class, - TestdataListFactorySortableEntityProvidingValue.class) + TestdataListFactorySortableEntityProvidingEntity.class) .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeFactoryEasyScoreCalculator.class) .withPhases(phaseConfig.config()); @@ -904,7 +1004,7 @@ void constructionHeuristicListVarEntityRangeAllocateToValueFromQueueFactory(Cons .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getStrength()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @@ -914,8 +1014,7 @@ void failConstructionHeuristicEntityRange() { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, - TestdataListSortableEntityProvidingEntity.class, - TestdataListSortableEntityProvidingValue.class) + TestdataListSortableEntityProvidingEntity.class) .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeEasyScoreCalculator.class) .withPhases( new ConstructionHeuristicPhaseConfig() @@ -942,6 +1041,89 @@ void failConstructionHeuristicEntityRange() { .hasMessageContaining("Maybe set the \"cacheType\" to STEP."); } + @Test + void failConstructionHeuristicListMixedProperties() { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidListSortableSolution.class, + TestdataInvalidListSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var solution = new TestdataInvalidListSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.list.sort.invalid.TestdataInvalidListSortableEntity) property (valueList)") + .hasMessageContaining( + "cannot have a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + } + + @Test + void failConstructionHeuristicMixedProperties() { + // Strength and Factory properties + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidMixedStrengthSortableSolution.class, + TestdataInvalidMixedStrengthSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var solution = new TestdataInvalidMixedStrengthSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableEntity) property (value)") + .hasMessageContaining( + "cannot have a strengthComparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "strengthWeightFactoryClass (ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory) at the same time."); + + // Comparator and Factory properties + var otherSolverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidMixedComparatorSortableSolution.class, + TestdataInvalidMixedComparatorSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var otherSolution = new TestdataInvalidMixedComparatorSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(otherSolverConfig, otherSolution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableEntity) property (value)") + .hasMessageContaining( + "cannot have a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + } + + @Test + void failConstructionHeuristicBothProperties() { + // Two comparator properties + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoComparatorSortableSolution.class, + TestdataInvalidTwoComparatorSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var solution = new TestdataInvalidTwoComparatorSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableEntity) property (value)") + .hasMessageContaining( + "cannot have a strengthComparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "nd a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator) at the same time."); + + // Comparator and Factory properties + var otherSolverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoFactorySortableSolution.class, + TestdataInvalidTwoFactorySortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var otherSolution = new TestdataInvalidTwoFactorySortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(otherSolverConfig, otherSolution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableEntity) property (value)") + .hasMessageContaining( + "cannot have a strengthWeightFactoryClass (ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory)") + .hasMessageContaining( + "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + } + @Test void failMixedModelDefaultConfiguration() { var solverConfig = PlannerTestUtils diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyHardSoftEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyHardSoftEasyScoreCalculator.java new file mode 100644 index 0000000000..3dda9fc6ab --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyHardSoftEasyScoreCalculator.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.testdomain.common; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class DummyHardSoftEasyScoreCalculator implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore calculateScore(@NonNull Object o) { + return HardSoftScore.ZERO; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java new file mode 100644 index 0000000000..ada61c42ef --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.testdomain.TestdataValue; + +public class DummyValueComparator implements Comparator { + + @Override + public int compare(TestdataValue v1, TestdataValue v2) { + return 0; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java new file mode 100644 index 0000000000..08390ba34e --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.testdomain.common; + +import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.testdomain.TestdataValue; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; + +public class DummyValueFactory + implements SorterFactory { + + @Override + public Comparable createSorter(TestdataListFactorySortableSolution solution, TestdataValue selection) { + return v -> 0; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java new file mode 100644 index 0000000000..6496822201 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.testdomain.common; + +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; +import ai.timefold.solver.core.testdomain.TestdataValue; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; + +public class DummyWeightValueFactory + implements SelectionSorterWeightFactory { + + @Override + public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, TestdataValue selection) { + return v -> 0; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java new file mode 100644 index 0000000000..6c7d606c4a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +public class TestSortableComparator implements Comparator { + + @Override + public int compare(TestSortableObject v1, TestSortableObject v2) { + return v1.getComparatorValue() - v2.getComparatorValue(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java new file mode 100644 index 0000000000..8b86d8b70b --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.testdomain.common; + +import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; + +public class TestSortableFactory + implements SelectionSorterWeightFactory, SorterFactory { + @Override + public Comparable createSorterWeight(Object o, TestSortableObject selection) { + return selection; + } + + @Override + public Comparable createSorter(Object o, TestSortableObject selection) { + return selection; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObject.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObject.java new file mode 100644 index 0000000000..aa750b90d4 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObject.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.testdomain.common; + +public interface TestSortableObject extends Comparable { + + int getComparatorValue(); + + @Override + default int compareTo(TestSortableObject o) { + return getComparatorValue() - o.getComparatorValue(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataSortableValue.java similarity index 54% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataSortableValue.java index ef6b2d49a6..037d464faf 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableValue.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataSortableValue.java @@ -1,8 +1,8 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; +package ai.timefold.solver.core.testdomain.common; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataSortableValue extends TestdataObject { +public class TestdataSortableValue extends TestdataObject implements TestSortableObject { private int strength; @@ -14,12 +14,8 @@ public TestdataSortableValue(String code, int strength) { this.strength = strength; } - public int getStrength() { + @Override + public int getComparatorValue() { return strength; } - - public void setStrength(int strength) { - this.strength = strength; - } - } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java deleted file mode 100644 index 8bb8dc635e..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableEntityComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.comparator; - -import java.util.Comparator; - -public class ListSortableEntityComparator implements Comparator { - - @Override - public int compare(TestdataListSortableEntity e1, TestdataListSortableEntity e2) { - return e1.getDifficulty() - e2.getDifficulty(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java deleted file mode 100644 index f636a0a571..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/ListSortableValueComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.comparator; - -import java.util.Comparator; - -public class ListSortableValueComparator implements Comparator { - - @Override - public int compare(TestdataListSortableValue v1, TestdataListSortableValue v2) { - return v1.getStrength() - v2.getStrength(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java index 33db3128aa..015ba9ef1c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java @@ -6,12 +6,15 @@ 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; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyComparatorClass = ListSortableEntityComparator.class) -public class TestdataListSortableEntity extends TestdataObject { +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataListSortableEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = ListSortableValueComparator.class) - private List valueList; + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + private List valueList; private int difficulty; public TestdataListSortableEntity() { @@ -23,19 +26,16 @@ public TestdataListSortableEntity(String code, int difficulty) { this.valueList = new ArrayList<>(); } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } - public int getDifficulty() { + @Override + public int getComparatorValue() { return difficulty; } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java index 5042a72764..809d024967 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableSolution.java @@ -9,9 +9,11 @@ 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.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution public class TestdataListSortableSolution { @@ -20,7 +22,7 @@ public static SolutionDescriptor buildSolutionDesc return SolutionDescriptor.buildSolutionDescriptor( TestdataListSortableSolution.class, TestdataListSortableEntity.class, - TestdataListSortableValue.class); + TestdataSortableValue.class); } public static TestdataListSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { @@ -28,7 +30,7 @@ public static TestdataListSortableSolution generateSolution(int valueCount, int .mapToObj(i -> new TestdataListSortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataListSortableValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList()); if (shuffle) { var random = new Random(0); @@ -41,17 +43,17 @@ public static TestdataListSortableSolution generateSolution(int valueCount, int return solution; } - private List valueList; + private List valueList; private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") - @PlanningEntityCollectionProperty - public List getValueList() { + @ProblemFactCollectionProperty + 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/sort/comparator/TestdataListSortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java deleted file mode 100644 index 1a7a52280b..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableValue.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.comparator; - -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 TestdataListSortableValue extends TestdataObject { - - private int strength; - - @InverseRelationShadowVariable(sourceVariableName = "valueList") - private TestdataListSortableEntity entity; - - public TestdataListSortableValue() { - } - - public TestdataListSortableValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - public TestdataListSortableEntity getEntity() { - return entity; - } - - public void setEntity(TestdataListSortableEntity entity) { - this.entity = entity; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java deleted file mode 100644 index eec862fb69..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableEntityFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class ListSortableEntityFactory - implements SorterFactory { - - @Override - public Comparable createSorter(TestdataListFactorySortableSolution solution, - TestdataListFactorySortableEntity selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java deleted file mode 100644 index eff999cfaa..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/ListSortableValueFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class ListSortableValueFactory - implements SorterFactory { - - @Override - public Comparable createSorter(TestdataListFactorySortableSolution solution, - TestdataListFactorySortableValue selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java index 9ec163c0de..d9b28a0cbc 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java @@ -6,12 +6,15 @@ 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; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyWeightFactoryClass = ListSortableEntityFactory.class) -public class TestdataListFactorySortableEntity extends TestdataObject implements Comparable { +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +public class TestdataListFactorySortableEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = ListSortableValueFactory.class) - private List valueList; + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + private List valueList; private int difficulty; public TestdataListFactorySortableEntity() { @@ -23,24 +26,16 @@ public TestdataListFactorySortableEntity(String code, int difficulty) { this.valueList = new ArrayList<>(); } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } - public int getDifficulty() { - return difficulty; - } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } - @Override - public int compareTo(TestdataListFactorySortableEntity o) { - return difficulty - o.difficulty; + public int getComparatorValue() { + return difficulty; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java index f5d77f582d..0ae5b91d18 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableSolution.java @@ -9,9 +9,11 @@ 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.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution public class TestdataListFactorySortableSolution { @@ -20,7 +22,7 @@ public static SolutionDescriptor buildSolut return SolutionDescriptor.buildSolutionDescriptor( TestdataListFactorySortableSolution.class, TestdataListFactorySortableEntity.class, - TestdataListFactorySortableValue.class); + TestdataSortableValue.class); } public static TestdataListFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { @@ -28,7 +30,7 @@ public static TestdataListFactorySortableSolution generateSolution(int valueCoun .mapToObj(i -> new TestdataListFactorySortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataListFactorySortableValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList()); if (shuffle) { var random = new Random(0); @@ -41,17 +43,17 @@ public static TestdataListFactorySortableSolution generateSolution(int valueCoun return solution; } - private List valueList; + private List valueList; private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") - @PlanningEntityCollectionProperty - public List getValueList() { + @ProblemFactCollectionProperty + 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/sort/factory/TestdataListFactorySortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java deleted file mode 100644 index 8ddfbdbd5a..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableValue.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.sort.factory; - -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 TestdataListFactorySortableValue extends TestdataObject implements Comparable { - - private int strength; - - @InverseRelationShadowVariable(sourceVariableName = "valueList") - private TestdataListFactorySortableEntity entity; - - public TestdataListFactorySortableValue() { - } - - public TestdataListFactorySortableValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - public TestdataListFactorySortableEntity getEntity() { - return entity; - } - - public void setEntity(TestdataListFactorySortableEntity entity) { - this.entity = entity; - } - - @Override - public int compareTo(TestdataListFactorySortableValue o) { - return strength - o.strength; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java new file mode 100644 index 0000000000..0e184321bb --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java @@ -0,0 +1,45 @@ +package ai.timefold.solver.core.testdomain.list.sort.invalid; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity +public class TestdataInvalidListSortableEntity extends TestdataObject { + + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, + comparatorFactoryClass = DummyValueFactory.class) + private List valueList; + private int difficulty; + + public TestdataInvalidListSortableEntity() { + } + + public TestdataInvalidListSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + this.valueList = new ArrayList<>(); + } + + 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/list/sort/invalid/TestdataInvalidListSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableSolution.java new file mode 100644 index 0000000000..2c20a86b62 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.list.sort.invalid; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidListSortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidListSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java deleted file mode 100644 index 5af8f813af..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableEntityComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; - -import java.util.Comparator; - -public class ListSortableEntityComparator implements Comparator { - - @Override - public int compare(TestdataListSortableEntityProvidingEntity e1, TestdataListSortableEntityProvidingEntity e2) { - return e1.getDifficulty() - e2.getDifficulty(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java deleted file mode 100644 index 85bcc73482..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/ListSortableValueComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; - -import java.util.Comparator; - -public class ListSortableValueComparator implements Comparator { - - @Override - public int compare(TestdataListSortableEntityProvidingValue v1, TestdataListSortableEntityProvidingValue v2) { - return v1.getStrength() - v2.getStrength(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java index 0c0a9304d3..d31c620292 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java @@ -8,15 +8,18 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyComparatorClass = ListSortableEntityComparator.class) -public class TestdataListSortableEntityProvidingEntity extends TestdataObject { +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataListSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = ListSortableValueComparator.class) - private List valueList; + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty - private List valueRange; + private List valueRange; private int difficulty; @@ -29,27 +32,24 @@ public TestdataListSortableEntityProvidingEntity(String code, int difficulty) { this.valueList = new ArrayList<>(); } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } - public List getValueRange() { + public List getValueRange() { return valueRange; } - public void setValueRange(List valueRange) { + public void setValueRange(List valueRange) { this.valueRange = valueRange; } - public int getDifficulty() { + @Override + public int getComparatorValue() { return difficulty; } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java index 8e5e9b12b4..86ed83d095 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingSolution.java @@ -11,6 +11,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution public class TestdataListSortableEntityProvidingSolution { @@ -19,7 +20,7 @@ public static SolutionDescriptor bu return SolutionDescriptor.buildSolutionDescriptor( TestdataListSortableEntityProvidingSolution.class, TestdataListSortableEntityProvidingEntity.class, - TestdataListSortableEntityProvidingValue.class); + TestdataSortableValue.class); } public static TestdataListSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, @@ -28,7 +29,7 @@ public static TestdataListSortableEntityProvidingSolution generateSolution(int v .mapToObj(i -> new TestdataListSortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataListSortableEntityProvidingValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); var random = new Random(0); var solution = new TestdataListSortableEntityProvidingSolution(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java deleted file mode 100644 index cd8f89889e..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingValue.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.comparator; - -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 TestdataListSortableEntityProvidingValue extends TestdataObject { - - private int strength; - - @InverseRelationShadowVariable(sourceVariableName = "valueList") - private TestdataListSortableEntityProvidingEntity entity; - - public TestdataListSortableEntityProvidingValue() { - } - - public TestdataListSortableEntityProvidingValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - public TestdataListSortableEntityProvidingEntity getEntity() { - return entity; - } - - public void setEntity(TestdataListSortableEntityProvidingEntity entity) { - this.entity = entity; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java deleted file mode 100644 index 46d8458f0f..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableEntityFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class ListSortableEntityFactory - implements - SorterFactory { - - @Override - public Comparable createSorter( - TestdataListFactorySortableEntityProvidingSolution solution, - TestdataListFactorySortableEntityProvidingEntity selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java deleted file mode 100644 index f009c9774a..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/ListSortableValueFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class ListSortableValueFactory - implements - SorterFactory { - - @Override - public Comparable createSorter( - TestdataListFactorySortableEntityProvidingSolution solution, - TestdataListFactorySortableEntityProvidingValue selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java index a8b9f1c00c..14e8a45593 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java @@ -8,16 +8,19 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyWeightFactoryClass = ListSortableEntityFactory.class) +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) public class TestdataListFactorySortableEntityProvidingEntity extends TestdataObject - implements Comparable { + implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = ListSortableValueFactory.class) - private List valueList; + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty - private List valueRange; + private List valueRange; private int difficulty; @@ -30,32 +33,24 @@ public TestdataListFactorySortableEntityProvidingEntity(String code, int difficu this.valueList = new ArrayList<>(); } - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } - public List getValueRange() { + public List getValueRange() { return valueRange; } - public void setValueRange(List valueRange) { + public void setValueRange(List valueRange) { this.valueRange = valueRange; } - public int getDifficulty() { - return difficulty; - } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } - @Override - public int compareTo(TestdataListFactorySortableEntityProvidingEntity o) { - return difficulty - o.difficulty; + public int getComparatorValue() { + return difficulty; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java index 8baa3b9968..c56e2ba78b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingSolution.java @@ -11,6 +11,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution public class TestdataListFactorySortableEntityProvidingSolution { @@ -19,7 +20,7 @@ public static SolutionDescriptor new TestdataListFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataListFactorySortableEntityProvidingValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); var solution = new TestdataListFactorySortableEntityProvidingSolution(); var random = new Random(0); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java deleted file mode 100644 index 2bf1907ef2..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingValue.java +++ /dev/null @@ -1,44 +0,0 @@ -package ai.timefold.solver.core.testdomain.list.valuerange.sort.factory; - -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 TestdataListFactorySortableEntityProvidingValue extends TestdataObject - implements Comparable { - - private int strength; - - @InverseRelationShadowVariable(sourceVariableName = "valueList") - private TestdataListFactorySortableEntityProvidingEntity entity; - - public TestdataListFactorySortableEntityProvidingValue() { - } - - public TestdataListFactorySortableEntityProvidingValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - public TestdataListFactorySortableEntityProvidingEntity getEntity() { - return entity; - } - - public void setEntity(TestdataListFactorySortableEntityProvidingEntity entity) { - this.entity = entity; - } - - @Override - public int compareTo(TestdataListFactorySortableEntityProvidingValue o) { - return strength - o.strength; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java deleted file mode 100644 index f2bf58c598..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableEntityComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; - -import java.util.Comparator; - -public class SortableEntityComparator implements Comparator { - - @Override - public int compare(TestdataSortableEntity e1, TestdataSortableEntity e2) { - return e1.getDifficulty() - e2.getDifficulty(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java deleted file mode 100644 index 98c91368f6..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/SortableValueComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; - -import java.util.Comparator; - -public class SortableValueComparator implements Comparator { - - @Override - public int compare(TestdataSortableValue v1, TestdataSortableValue v2) { - return v1.getStrength() - v2.getStrength(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java new file mode 100644 index 0000000000..7afc6738e0 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java @@ -0,0 +1,29 @@ +package ai.timefold.solver.core.testdomain.sort.comparator; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableSolution; + +import org.jspecify.annotations.NonNull; + +public class NewOneValuePerEntityEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore calculateScore(@NonNull TestdataNewSortableSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataNewSortableEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataNewSortableEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java new file mode 100644 index 0000000000..7ebde4617c --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; + +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; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataNewSortableEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataNewSortableEntity() { + } + + public TestdataNewSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java similarity index 70% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java index bdf7b2db38..6d1d379629 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; +package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; import java.util.ArrayList; import java.util.Collections; @@ -13,20 +13,21 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataSortableSolution { +public class TestdataNewSortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataSortableSolution.class, - TestdataSortableEntity.class, + TestdataNewSortableSolution.class, + TestdataNewSortableEntity.class, TestdataSortableValue.class); } - public static TestdataSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataNewSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataSortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataNewSortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) @@ -36,14 +37,14 @@ public static TestdataSortableSolution generateSolution(int valueCount, int enti Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataSortableSolution solution = new TestdataSortableSolution(); + TestdataNewSortableSolution solution = new TestdataNewSortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -57,11 +58,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -74,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataSortableEntity entity) { + public void removeEntity(TestdataNewSortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java similarity index 65% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java index 5339ac2c39..ea74449af5 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; +package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; import java.util.Objects; @@ -7,18 +7,18 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityEasyScoreCalculator - implements EasyScoreCalculator { +public class OldOneValuePerEntityEasyScoreCalculator + implements EasyScoreCalculator { @Override - public @NonNull HardSoftScore calculateScore(@NonNull TestdataSortableSolution solution) { + public @NonNull HardSoftScore calculateScore(@NonNull TestdataOldSortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataSortableEntity::getValue) + .map(TestdataOldSortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataSortableEntity::getValue) + .map(TestdataOldSortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java new file mode 100644 index 0000000000..bf677dbb48 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; + +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; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataOldSortableEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataOldSortableEntity() { + } + + public TestdataOldSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java similarity index 60% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java index 4d899f9f99..b60c017688 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory; +package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; import java.util.ArrayList; import java.util.Collections; @@ -13,55 +13,56 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactorySortableSolution { +public class TestdataOldSortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactorySortableSolution.class, - TestdataFactorySortableEntity.class, - TestdataFactorySortableValue.class); + TestdataOldSortableSolution.class, + TestdataOldSortableEntity.class, + TestdataSortableValue.class); } - public static TestdataFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataOldSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactorySortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataOldSortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataFactorySortableValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList()); if (shuffle) { var random = new Random(0); Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataFactorySortableSolution solution = new TestdataFactorySortableSolution(); + TestdataOldSortableSolution solution = new TestdataOldSortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } - private List valueList; - private List entityList; + private List valueList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @ProblemFactCollectionProperty - public List getValueList() { + public List getValueList() { return valueList; } - public void setValueList(List valueList) { + public void setValueList(List valueList) { this.valueList = valueList; } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -74,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactorySortableEntity entity) { + public void removeEntity(TestdataOldSortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java deleted file mode 100644 index 52e8244b0b..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableEntityFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class SortableEntityFactory - implements SorterFactory { - - @Override - public Comparable createSorter(TestdataFactorySortableSolution solution, - TestdataFactorySortableEntity selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java deleted file mode 100644 index b643ae7a47..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/SortableValueFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.factory; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; - -public class SortableValueFactory - implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataFactorySortableSolution solution, TestdataFactorySortableValue selection) { - return selection; - } - -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java deleted file mode 100644 index a6d03977c8..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.factory; - -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(difficultyWeightFactoryClass = SortableEntityFactory.class) -public class TestdataFactorySortableEntity extends TestdataObject implements Comparable { - - @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = SortableValueFactory.class) - private TestdataFactorySortableValue value; - private int difficulty; - - public TestdataFactorySortableEntity() { - } - - public TestdataFactorySortableEntity(String code, int difficulty) { - super(code); - this.difficulty = difficulty; - } - - public TestdataFactorySortableValue getValue() { - return value; - } - - public void setValue(TestdataFactorySortableValue value) { - this.value = value; - } - - public int getDifficulty() { - return difficulty; - } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } - - @Override - public int compareTo(TestdataFactorySortableEntity o) { - return difficulty - o.difficulty; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java deleted file mode 100644 index 1564e55c2a..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableValue.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.solver.core.testdomain.sort.factory; - -import ai.timefold.solver.core.testdomain.TestdataObject; - -public class TestdataFactorySortableValue extends TestdataObject implements Comparable { - - private int strength; - - public TestdataFactorySortableValue() { - } - - public TestdataFactorySortableValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - @Override - public int compareTo(TestdataFactorySortableValue o) { - return strength - o.strength; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java similarity index 60% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java index 19d08a52e9..0de749ccf3 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory; +package ai.timefold.solver.core.testdomain.sort.factory.newapproach; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class NewOneValuePerEntityFactoryEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataFactorySortableSolution solution) { + calculateScore(@NonNull TestdataFactoryNewSortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactorySortableEntity::getValue) + .map(TestdataFactoryNewSortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactorySortableEntity::getValue) + .map(TestdataFactoryNewSortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java new file mode 100644 index 0000000000..835ffc0430 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.core.testdomain.sort.factory.newapproach; + +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; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +public class TestdataFactoryNewSortableEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataFactoryNewSortableEntity() { + } + + public TestdataFactoryNewSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java new file mode 100644 index 0000000000..5d41258706 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java @@ -0,0 +1,83 @@ +package ai.timefold.solver.core.testdomain.sort.factory.newapproach; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataFactoryNewSortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataFactoryNewSortableSolution.class, + TestdataFactoryNewSortableEntity.class, + TestdataSortableValue.class); + } + + public static TestdataFactoryNewSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataFactoryNewSortableEntity("Generated Entity " + i, i)) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } + TestdataFactoryNewSortableSolution solution = new TestdataFactoryNewSortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @ProblemFactCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataFactoryNewSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..f2fb3b4af9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OldOneValuePerEntityFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataFactoryOldSortableSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataFactoryOldSortableEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataFactoryOldSortableEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java new file mode 100644 index 0000000000..dfe74589a0 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java @@ -0,0 +1,37 @@ +package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; + +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; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +public class TestdataFactoryOldSortableEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = TestSortableFactory.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataFactoryOldSortableEntity() { + } + + public TestdataFactoryOldSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java new file mode 100644 index 0000000000..7891bb0dd0 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java @@ -0,0 +1,83 @@ +package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataFactoryOldSortableSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataFactoryOldSortableSolution.class, + TestdataFactoryOldSortableEntity.class, + TestdataSortableValue.class); + } + + public static TestdataFactoryOldSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataFactoryOldSortableEntity("Generated Entity " + i, i)) + .toList()); + var valueList = new ArrayList<>(IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) + .toList()); + if (shuffle) { + var random = new Random(0); + Collections.shuffle(entityList, random); + Collections.shuffle(valueList, random); + } + TestdataFactoryOldSortableSolution solution = new TestdataFactoryOldSortableSolution(); + solution.setValueList(valueList); + solution.setEntityList(entityList); + return solution; + } + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @ProblemFactCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataFactoryOldSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableEntity.java new file mode 100644 index 0000000000..b664a20a7d --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableEntity.java @@ -0,0 +1,41 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity +public class TestdataInvalidMixedComparatorSortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, + comparatorFactoryClass = DummyValueFactory.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataInvalidMixedComparatorSortableEntity() { + } + + public TestdataInvalidMixedComparatorSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableSolution.java new file mode 100644 index 0000000000..89ada324c7 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/comparator/TestdataInvalidMixedComparatorSortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidMixedComparatorSortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidMixedComparatorSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableEntity.java similarity index 52% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableEntity.java index 07c605dd91..1e9a860520 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableEntity.java @@ -1,20 +1,24 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; +package ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength; 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; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyComparatorClass = SortableEntityComparator.class) -public class TestdataSortableEntity extends TestdataObject { +@PlanningEntity +public class TestdataInvalidMixedStrengthSortableEntity extends TestdataObject { - @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = SortableValueComparator.class) + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = DummyValueComparator.class, + strengthWeightFactoryClass = DummyWeightValueFactory.class) private TestdataSortableValue value; private int difficulty; - public TestdataSortableEntity() { + public TestdataInvalidMixedStrengthSortableEntity() { } - public TestdataSortableEntity(String code, int difficulty) { + public TestdataInvalidMixedStrengthSortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableSolution.java new file mode 100644 index 0000000000..3b65d483d2 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/mixed/strength/TestdataInvalidMixedStrengthSortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidMixedStrengthSortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidMixedStrengthSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java new file mode 100644 index 0000000000..c7906950fb --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity +public class TestdataInvalidTwoComparatorSortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, + strengthComparatorClass = DummyValueComparator.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataInvalidTwoComparatorSortableEntity() { + } + + public TestdataInvalidTwoComparatorSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java new file mode 100644 index 0000000000..897bb02d3f --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidTwoComparatorSortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidTwoComparatorSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java new file mode 100644 index 0000000000..399fb78ac3 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java @@ -0,0 +1,41 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity +public class TestdataInvalidTwoFactorySortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = DummyValueFactory.class, + strengthWeightFactoryClass = DummyWeightValueFactory.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataInvalidTwoFactorySortableEntity() { + } + + public TestdataInvalidTwoFactorySortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java new file mode 100644 index 0000000000..a3f5d71c06 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidTwoFactorySortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidTwoFactorySortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java deleted file mode 100644 index 1ee219fc72..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableEntityComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; - -import java.util.Comparator; - -public class SortableEntityComparator implements Comparator { - - @Override - public int compare(TestdataSortableEntityProvidingEntity e1, TestdataSortableEntityProvidingEntity e2) { - return e1.getDifficulty() - e2.getDifficulty(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java deleted file mode 100644 index bf2b7f6fb6..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/SortableValueComparator.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; - -import java.util.Comparator; - -public class SortableValueComparator implements Comparator { - - @Override - public int compare(TestdataSortableEntityProvidingValue v1, TestdataSortableEntityProvidingValue v2) { - return v1.getStrength() - v2.getStrength(); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java deleted file mode 100644 index e00e531ee2..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingEntity.java +++ /dev/null @@ -1,53 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity(difficultyComparatorClass = SortableEntityComparator.class) -public class TestdataSortableEntityProvidingEntity extends TestdataObject { - - @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = SortableValueComparator.class) - private TestdataSortableEntityProvidingValue value; - @ValueRangeProvider(id = "valueRange") - @PlanningEntityCollectionProperty - private List valueRange; - - private int difficulty; - - public TestdataSortableEntityProvidingEntity() { - } - - public TestdataSortableEntityProvidingEntity(String code, int difficulty) { - super(code); - this.difficulty = difficulty; - } - - public TestdataSortableEntityProvidingValue getValue() { - return value; - } - - public void setValue(TestdataSortableEntityProvidingValue value) { - this.value = value; - } - - public List getValueRange() { - return valueRange; - } - - public void setValueRange(List valueRange) { - this.valueRange = valueRange; - } - - 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/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java deleted file mode 100644 index 69b694db38..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingValue.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; - -import ai.timefold.solver.core.testdomain.TestdataObject; - -public class TestdataSortableEntityProvidingValue extends TestdataObject { - - private int strength; - - public TestdataSortableEntityProvidingValue() { - } - - public TestdataSortableEntityProvidingValue(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/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java similarity index 63% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java index 7bca2b3d45..340b254c14 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityRangeEasyScoreCalculator - implements EasyScoreCalculator { +public class NewOneValuePerEntityRangeEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataSortableEntityProvidingSolution solution) { + calculateScore(@NonNull TestdataNewSortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataSortableEntityProvidingEntity::getValue) + .map(TestdataNewSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataSortableEntityProvidingEntity::getValue) + .map(TestdataNewSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..bd5041e9f5 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java @@ -0,0 +1,53 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataNewSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) + private TestdataSortableValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataNewSortableEntityProvidingEntity() { + } + + public TestdataNewSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java similarity index 60% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java index aa52f438c9..5b46cfcaf3 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; import java.util.ArrayList; import java.util.Collections; @@ -11,27 +11,27 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataSortableEntityProvidingSolution { +public class TestdataNewSortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataSortableEntityProvidingSolution.class, - TestdataSortableEntityProvidingEntity.class, - TestdataSortableEntityProvidingValue.class); + TestdataNewSortableEntityProvidingSolution.class, + TestdataNewSortableEntityProvidingEntity.class); } - public static TestdataSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, - boolean shuffle) { + public static TestdataNewSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataSortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataNewSortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataSortableEntityProvidingValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); var random = new Random(0); - var solution = new TestdataSortableEntityProvidingSolution(); + var solution = new TestdataNewSortableEntityProvidingSolution(); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); if (shuffle) { @@ -46,15 +46,15 @@ public static TestdataSortableEntityProvidingSolution generateSolution(int value return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataSortableEntityProvidingEntity entity) { + public void removeEntity(TestdataNewSortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java new file mode 100644 index 0000000000..bc17befbc1 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OldOneValuePerEntityRangeEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore(@NonNull TestdataOldSortableEntityProvidingSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataOldSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataOldSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..852aba2216 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java @@ -0,0 +1,53 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableComparator; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +public class TestdataOldSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) + private TestdataSortableValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataOldSortableEntityProvidingEntity() { + } + + public TestdataOldSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java new file mode 100644 index 0000000000..4c8749c5c3 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java @@ -0,0 +1,75 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataOldSortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataOldSortableEntityProvidingSolution.class, + TestdataOldSortableEntityProvidingEntity.class); + } + + public static TestdataOldSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataOldSortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList()); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) + .toList(); + var random = new Random(0); + var solution = new TestdataOldSortableEntityProvidingSolution(); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + if (shuffle) { + Collections.shuffle(valueRange, random); + } + entity.setValueRange(valueRange); + } + if (shuffle) { + Collections.shuffle(entityList, random); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private HardSoftScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataOldSortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java deleted file mode 100644 index 4f98918326..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableEntityFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; - -import ai.timefold.solver.core.api.domain.common.SorterFactory; - -public class SortableEntityFactory - implements - SorterFactory { - - @Override - public Comparable createSorter( - TestdataFactorySortableEntityProvidingSolution solution, - TestdataFactorySortableEntityProvidingEntity selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java deleted file mode 100644 index 17f4bf3ea5..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/SortableValueFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; - -public class SortableValueFactory - implements - SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataFactorySortableEntityProvidingSolution solution, - TestdataFactorySortableEntityProvidingValue selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java deleted file mode 100644 index 76d606c543..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java +++ /dev/null @@ -1,59 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.testdomain.TestdataObject; - -@PlanningEntity(difficultyWeightFactoryClass = SortableEntityFactory.class) -public class TestdataFactorySortableEntityProvidingEntity extends TestdataObject - implements Comparable { - - @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = SortableValueFactory.class) - private TestdataFactorySortableEntityProvidingValue value; - @ValueRangeProvider(id = "valueRange") - @PlanningEntityCollectionProperty - private List valueRange; - - private int difficulty; - - public TestdataFactorySortableEntityProvidingEntity() { - } - - public TestdataFactorySortableEntityProvidingEntity(String code, int difficulty) { - super(code); - this.difficulty = difficulty; - } - - public TestdataFactorySortableEntityProvidingValue getValue() { - return value; - } - - public void setValue(TestdataFactorySortableEntityProvidingValue value) { - this.value = value; - } - - public List getValueRange() { - return valueRange; - } - - public void setValueRange(List valueRange) { - this.valueRange = valueRange; - } - - public int getDifficulty() { - return difficulty; - } - - public void setDifficulty(int difficulty) { - this.difficulty = difficulty; - } - - @Override - public int compareTo(TestdataFactorySortableEntityProvidingEntity o) { - return difficulty - o.difficulty; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java deleted file mode 100644 index 45705e6431..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingValue.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; - -import ai.timefold.solver.core.testdomain.TestdataObject; - -public class TestdataFactorySortableEntityProvidingValue extends TestdataObject - implements Comparable { - - private int strength; - - public TestdataFactorySortableEntityProvidingValue() { - } - - public TestdataFactorySortableEntityProvidingValue(String code, int strength) { - super(code); - this.strength = strength; - } - - public int getStrength() { - return strength; - } - - public void setStrength(int strength) { - this.strength = strength; - } - - @Override - public int compareTo(TestdataFactorySortableEntityProvidingValue o) { - return strength - o.strength; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java similarity index 63% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java index f121259d6e..d5fbb3d332 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityRangeFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; import java.util.Objects; @@ -7,20 +7,20 @@ import org.jspecify.annotations.NonNull; -public class OneValuePerEntityRangeFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class NewOneValuePerEntityRangeFactoryEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore calculateScore( - @NonNull TestdataFactorySortableEntityProvidingSolution solution) { + @NonNull TestdataFactoryNewSortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactorySortableEntityProvidingEntity::getValue) + .map(TestdataFactoryNewSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactorySortableEntityProvidingEntity::getValue) + .map(TestdataFactoryNewSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..d8a9d0310c --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java @@ -0,0 +1,54 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +public class TestdataFactoryNewSortableEntityProvidingEntity extends TestdataObject + implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + private TestdataSortableValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataFactoryNewSortableEntityProvidingEntity() { + } + + public TestdataFactoryNewSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java similarity index 58% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java index ee3bae5bd9..2702345a2e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory; +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; import java.util.ArrayList; import java.util.Collections; @@ -11,26 +11,26 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactorySortableEntityProvidingSolution { +public class TestdataFactoryNewSortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactorySortableEntityProvidingSolution.class, - TestdataFactorySortableEntityProvidingEntity.class, - TestdataFactorySortableEntityProvidingValue.class); + TestdataFactoryNewSortableEntityProvidingSolution.class, + TestdataFactoryNewSortableEntityProvidingEntity.class); } - public static TestdataFactorySortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, - boolean shuffle) { + public static TestdataFactoryNewSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataFactoryNewSortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) - .mapToObj(i -> new TestdataFactorySortableEntityProvidingValue("Generated Value " + i, i)) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); - var solution = new TestdataFactorySortableEntityProvidingSolution(); + var solution = new TestdataFactoryNewSortableEntityProvidingSolution(); var random = new Random(0); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); @@ -46,15 +46,15 @@ public static TestdataFactorySortableEntityProvidingSolution generateSolution(in return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactorySortableEntityProvidingEntity entity) { + public void removeEntity(TestdataFactoryNewSortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java new file mode 100644 index 0000000000..05e09ae1be --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java @@ -0,0 +1,29 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; + +import java.util.Objects; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; + +import org.jspecify.annotations.NonNull; + +public class OldOneValuePerEntityRangeFactoryEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull HardSoftScore + calculateScore( + @NonNull TestdataFactoryOldSortableEntityProvidingSolution solution) { + var distinct = (int) solution.getEntityList().stream() + .map(TestdataFactoryOldSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataFactoryOldSortableEntityProvidingEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + return HardSoftScore.of(-repeated, -distinct); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java new file mode 100644 index 0000000000..e156180e05 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java @@ -0,0 +1,54 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; + +import java.util.List; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.testdomain.TestdataObject; +import ai.timefold.solver.core.testdomain.common.TestSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +public class TestdataFactoryOldSortableEntityProvidingEntity extends TestdataObject + implements TestSortableObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = TestSortableFactory.class) + private TestdataSortableValue value; + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + private List valueRange; + + private int difficulty; + + public TestdataFactoryOldSortableEntityProvidingEntity() { + } + + public TestdataFactoryOldSortableEntityProvidingEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + public List getValueRange() { + return valueRange; + } + + public void setValueRange(List valueRange) { + this.valueRange = valueRange; + } + + @Override + public int getComparatorValue() { + return difficulty; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java new file mode 100644 index 0000000000..d60bb5dec9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java @@ -0,0 +1,75 @@ +package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +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.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataFactoryOldSortableEntityProvidingSolution { + + public static SolutionDescriptor buildSolutionDescriptor() { + return SolutionDescriptor.buildSolutionDescriptor( + TestdataFactoryOldSortableEntityProvidingSolution.class, + TestdataFactoryOldSortableEntityProvidingEntity.class); + } + + public static TestdataFactoryOldSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + boolean shuffle) { + var entityList = new ArrayList<>(IntStream.range(0, entityCount) + .mapToObj(i -> new TestdataFactoryOldSortableEntityProvidingEntity("Generated Entity " + i, i)) + .toList()); + var valueList = IntStream.range(0, valueCount) + .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) + .toList(); + var solution = new TestdataFactoryOldSortableEntityProvidingSolution(); + var random = new Random(0); + for (var entity : entityList) { + var valueRange = new ArrayList<>(valueList); + if (shuffle) { + Collections.shuffle(valueRange, random); + } + entity.setValueRange(valueRange); + } + if (shuffle) { + Collections.shuffle(entityList, random); + } + solution.setEntityList(entityList); + return solution; + } + + private List entityList; + private HardSoftScore score; + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataFactoryOldSortableEntityProvidingEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} From 669ba5381277acb911243adb363f68cbddd15c1b Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 14 Oct 2025 12:30:57 -0300 Subject: [PATCH 15/33] chore: update entity sorting settings --- benchmark/src/main/resources/benchmark.xsd | 18 + core/src/build/revapi-differences.json | 24 +- ...terFactory.java => ComparatorFactory.java} | 2 +- .../api/domain/entity/PlanningEntity.java | 47 ++- .../domain/variable/PlanningListVariable.java | 6 +- .../api/domain/variable/PlanningVariable.java | 13 +- .../ConstructionHeuristicType.java | 48 +-- .../selector/entity/EntitySelectorConfig.java | 49 ++- .../selector/entity/EntitySorterManner.java | 12 +- .../selector/move/MoveSelectorConfig.java | 10 +- .../selector/value/ValueSelectorConfig.java | 64 ++-- .../selector/value/ValueSorterManner.java | 22 +- .../entity/descriptor/EntityDescriptor.java | 73 ++-- .../descriptor/BasicVariableDescriptor.java | 27 +- .../descriptor/GenuineVariableDescriptor.java | 6 +- .../decorator/FactorySelectionSorter.java | 16 +- .../SelectionSorterWeightFactory.java | 6 +- .../entity/EntitySelectorFactory.java | 6 +- .../list/DestinationSelectorFactory.java | 4 +- .../move/AbstractMoveSelectorFactory.java | 6 +- .../selector/value/ValueSelectorFactory.java | 6 +- core/src/main/resources/solver.xsd | 12 + ...DefaultConstructionHeuristicPhaseTest.java | 339 ++++++++++++++++-- .../decorator/FactorySelectionSorterTest.java | 6 +- .../entity/EntitySelectorFactoryTest.java | 8 +- .../value/ValueSelectorFactoryTest.java | 8 +- .../testdomain/common/DummyValueFactory.java | 4 +- .../common/TestSortableFactory.java | 4 +- .../TestdataDifficultyFactory.java | 4 +- .../TestdataNewSortableEntity.java | 2 +- .../TestdataFactoryNewSortableEntity.java | 2 +- ...alidTwoEntityComparatorSortableEntity.java | 39 ++ ...idTwoEntityComparatorSortableSolution.java | 52 +++ ...alidTwoValueComparatorSortableEntity.java} | 8 +- ...idTwoValueComparatorSortableSolution.java} | 12 +- ...InvalidTwoEntityFactorySortableEntity.java | 39 ++ ...validTwoEntityFactorySortableSolution.java | 52 +++ ...InvalidTwoValueFactorySortableEntity.java} | 8 +- ...validTwoValueFactorySortableSolution.java} | 12 +- .../optimization-algorithms/overview.adoc | 10 +- .../resources/META-INF/rewrite/ToLatest.yml | 2 +- 41 files changed, 813 insertions(+), 275 deletions(-) rename core/src/main/java/ai/timefold/solver/core/api/domain/common/{SorterFactory.java => ComparatorFactory.java} (97%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableSolution.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/{TestdataInvalidTwoComparatorSortableEntity.java => value/TestdataInvalidTwoValueComparatorSortableEntity.java} (80%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/{TestdataInvalidTwoComparatorSortableSolution.java => value/TestdataInvalidTwoValueComparatorSortableSolution.java} (75%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableEntity.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableSolution.java rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/{TestdataInvalidTwoFactorySortableEntity.java => value/TestdataInvalidTwoValueFactorySortableEntity.java} (82%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/{TestdataInvalidTwoFactorySortableSolution.java => value/TestdataInvalidTwoValueFactorySortableSolution.java} (76%) diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index 57f465034a..a0af3558af 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -2874,6 +2874,12 @@ + + + + + + @@ -2901,6 +2907,18 @@ + + + + + + + + + + + + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index 6d162e8d46..3f13b3d264 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -353,46 +353,46 @@ "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", - "justification": "New weight factory base class" + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()", + "justification": "New comparator factory class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", - "justification": "New weight factory base class" + "justification": "New comparator factory class" }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", - "justification": "New weight factory base class" + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::getSorterWeightFactoryClass()", + "justification": "New comparator factory class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", - "justification": "New weight factory base class" + "justification": "New comparator factory class" }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", "old": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", - "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", - "justification": "New weight factory base class" + "new": "method java.lang.Class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()", + "justification": "New comparator factory class" }, { "ignore": true, "code": "java.method.parameterTypeParameterChanged", "old": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", - "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", + "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", - "justification": "New weight factory base class" + "justification": "New comparator factory class" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java rename to core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java index 75c6fd8497..fefc2f11d8 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/SorterFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java @@ -34,7 +34,7 @@ */ @NullMarked @FunctionalInterface -public interface SorterFactory { +public interface ComparatorFactory { /** * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index a1e6994b89..ddb90a46ff 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -7,7 +7,7 @@ import java.lang.annotation.Target; import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -69,27 +69,64 @@ interface NullPinningFilter extends PinningFilter { *

* Do not use together with {@link #difficultyWeightFactoryClass()}. * + * @deprecated Deprecated in favor of {@link #comparatorClass()}. + * * @return {@link NullDifficultyComparator} when it is null (workaround for annotation limitation) * @see #difficultyWeightFactoryClass() */ + @Deprecated(forRemoval = true, since = "1.28.0") Class difficultyComparatorClass() default NullDifficultyComparator.class; + /** + * Allows sorting a collection of planning entities for this variable. + * Some algorithms perform better when the entities are sorted based on specific metrics. + *

+ * The {@link Comparator} should sort the data in ascending order. + * For example, prioritize three vehicles by sorting them based on their capacity: + * Vehicle C (4 people), Vehicle A (6 people), Vehicle B (32 people) + *

+ * Do not use together with {@link #comparatorFactoryClass()}. + * + * @return {@link PlanningVariable.NullComparator} when it is null (workaround for annotation limitation) + * @see #comparatorFactoryClass() + */ + Class comparatorClass() default NullComparator.class; + /** Workaround for annotation limitation in {@link #difficultyComparatorClass()}. */ - interface NullDifficultyComparator extends Comparator { + interface NullDifficultyComparator extends NullComparator { + } + + interface NullComparator extends Comparator { } /** - * The {@link SorterFactory} alternative for {@link #difficultyComparatorClass()}. + * The {@link ComparatorFactory} alternative for {@link #difficultyComparatorClass()}. *

* Do not use together with {@link #difficultyComparatorClass()}. * + * @deprecated Deprecated in favor of {@link #comparatorFactoryClass()}. + * * @return {@link NullDifficultyWeightFactory} when it is null (workaround for annotation limitation) * @see #difficultyComparatorClass() */ - Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; + @Deprecated(forRemoval = true, since = "1.28.0") + Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; + + /** + * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. + *

+ * Do not use together with {@link #comparatorClass()}. + * + * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) + * @see #comparatorClass() + */ + Class comparatorFactoryClass() default NullComparatorFactory.class; /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */ - interface NullDifficultyWeightFactory extends SorterFactory { + interface NullDifficultyWeightFactory extends NullComparatorFactory { + } + + interface NullComparatorFactory extends ComparatorFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java index 1c3ed35178..3ada275a51 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java @@ -9,7 +9,7 @@ import java.util.Comparator; import java.util.List; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; 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; @@ -75,13 +75,13 @@ Class comparatorClass() default NullComparator.class; /** - * The {@link SorterFactory} alternative for {@link #comparatorClass()}. + * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. *

* Do not use together with {@link #comparatorClass()}. * * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) * @see #comparatorClass() */ - Class comparatorFactoryClass() default NullComparatorFactory.class; + Class comparatorFactoryClass() default NullComparatorFactory.class; } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index f3c5653a2e..2484782321 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -8,11 +8,10 @@ import java.lang.annotation.Target; import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms. @@ -110,7 +109,7 @@ interface NullComparator extends Comparator { } /** - * The {@link SelectionSorterWeightFactory} alternative for {@link #strengthComparatorClass()}. + * The {@link ComparatorFactory} alternative for {@link #strengthComparatorClass()}. *

* Do not use together with {@link #strengthComparatorClass()}. * @@ -120,23 +119,23 @@ interface NullComparator extends Comparator { * @see #strengthComparatorClass() */ @Deprecated(forRemoval = true, since = "1.28.0") - Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; + Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; /** - * The {@link SorterFactory} alternative for {@link #comparatorClass()}. + * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. *

* Do not use together with {@link #comparatorClass()}. * * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) * @see #comparatorClass() */ - Class comparatorFactoryClass() default NullComparatorFactory.class; + Class comparatorFactoryClass() default NullComparatorFactory.class; /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ interface NullStrengthWeightFactory extends NullComparatorFactory { } - interface NullComparatorFactory extends SelectionSorterWeightFactory { + interface NullComparatorFactory extends ComparatorFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java index e1af4741ac..030c249ddb 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java +++ b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java @@ -57,44 +57,22 @@ public enum ConstructionHeuristicType { ALLOCATE_FROM_POOL; public @NonNull EntitySorterManner getDefaultEntitySorterManner() { - switch (this) { - case FIRST_FIT: - case WEAKEST_FIT: - case STRONGEST_FIT: - return EntitySorterManner.NONE; - case FIRST_FIT_DECREASING: - case WEAKEST_FIT_DECREASING: - case STRONGEST_FIT_DECREASING: - return EntitySorterManner.DECREASING_DIFFICULTY; - case ALLOCATE_ENTITY_FROM_QUEUE: - case ALLOCATE_TO_VALUE_FROM_QUEUE: - case CHEAPEST_INSERTION: - case ALLOCATE_FROM_POOL: - return EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE; - default: - throw new IllegalStateException("The constructionHeuristicType (" + this + ") is not implemented."); - } + return switch (this) { + case FIRST_FIT, WEAKEST_FIT, STRONGEST_FIT -> EntitySorterManner.NONE; + case FIRST_FIT_DECREASING, WEAKEST_FIT_DECREASING, STRONGEST_FIT_DECREASING -> EntitySorterManner.DESCENDING; + case ALLOCATE_ENTITY_FROM_QUEUE, ALLOCATE_TO_VALUE_FROM_QUEUE, CHEAPEST_INSERTION, ALLOCATE_FROM_POOL -> + EntitySorterManner.DESCENDING_IF_AVAILABLE; + }; } public @NonNull ValueSorterManner getDefaultValueSorterManner() { - switch (this) { - case FIRST_FIT: - case FIRST_FIT_DECREASING: - return ValueSorterManner.NONE; - case WEAKEST_FIT: - case WEAKEST_FIT_DECREASING: - return ValueSorterManner.INCREASING_STRENGTH; - case STRONGEST_FIT: - case STRONGEST_FIT_DECREASING: - return ValueSorterManner.DECREASING_STRENGTH; - case ALLOCATE_ENTITY_FROM_QUEUE: - case ALLOCATE_TO_VALUE_FROM_QUEUE: - case CHEAPEST_INSERTION: - case ALLOCATE_FROM_POOL: - return ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE; - default: - throw new IllegalStateException("The constructionHeuristicType (" + this + ") is not implemented."); - } + return switch (this) { + case FIRST_FIT, FIRST_FIT_DECREASING -> ValueSorterManner.NONE; + case WEAKEST_FIT, WEAKEST_FIT_DECREASING -> ValueSorterManner.ASCENDING; + case STRONGEST_FIT, STRONGEST_FIT_DECREASING -> ValueSorterManner.DESCENDING; + case ALLOCATE_ENTITY_FROM_QUEUE, ALLOCATE_TO_VALUE_FROM_QUEUE, CHEAPEST_INSERTION, ALLOCATE_FROM_POOL -> + ValueSorterManner.ASCENDING_IF_AVAILABLE; + }; } /** diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index 1ae1fa357a..dd1deb03e0 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -63,7 +63,7 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe protected EntitySorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -156,11 +156,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -247,7 +247,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull EntitySelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } @@ -331,38 +331,29 @@ public String toString() { public static boolean hasSorter(@NonNull EntitySorterManner entitySorterManner, @NonNull EntityDescriptor entityDescriptor) { - switch (entitySorterManner) { - case NONE: - return false; - case DECREASING_DIFFICULTY: - return true; - case DECREASING_DIFFICULTY_IF_AVAILABLE: - return entityDescriptor.getDecreasingDifficultySorter() != null; - default: - throw new IllegalStateException("The sorterManner (" - + entitySorterManner + ") is not implemented."); - } + return switch (entitySorterManner) { + case NONE -> false; + case DECREASING_DIFFICULTY, DESCENDING -> true; + case DECREASING_DIFFICULTY_IF_AVAILABLE, DESCENDING_IF_AVAILABLE -> + entityDescriptor.getDescendingSorter() != null; + }; } public static @NonNull SelectionSorter determineSorter( @NonNull EntitySorterManner entitySorterManner, @NonNull EntityDescriptor entityDescriptor) { - SelectionSorter sorter; - switch (entitySorterManner) { + return switch (entitySorterManner) { case NONE: throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); - case DECREASING_DIFFICULTY, DECREASING_DIFFICULTY_IF_AVAILABLE: - sorter = (SelectionSorter) entityDescriptor.getDecreasingDifficultySorter(); + case DECREASING_DIFFICULTY, DECREASING_DIFFICULTY_IF_AVAILABLE, DESCENDING, DESCENDING_IF_AVAILABLE: + var sorter = (SelectionSorter) entityDescriptor.getDescendingSorter(); if (sorter == null) { - throw new IllegalArgumentException("The sorterManner (" + entitySorterManner - + ") on entity class (" + entityDescriptor.getEntityClass() - + ") fails because that entity class's @" + PlanningEntity.class.getSimpleName() - + " annotation does not declare any difficulty comparison."); + throw new IllegalArgumentException( + "The sorterManner (%s) on entity class (%s) fails because that entity class's @%s annotation does not declare any difficulty comparison." + .formatted(entitySorterManner, entityDescriptor.getEntityClass(), + PlanningEntity.class.getSimpleName())); } - return sorter; - default: - throw new IllegalStateException("The sorterManner (" - + entitySorterManner + ") is not implemented."); - } + yield sorter; + }; } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java index 02762b65ee..7a54571e9c 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java @@ -11,6 +11,16 @@ @XmlEnum public enum EntitySorterManner { NONE, + /** + * @deprecated use {@link #DESCENDING} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") DECREASING_DIFFICULTY, - DECREASING_DIFFICULTY_IF_AVAILABLE; + /** + * @deprecated use {@link #DESCENDING_IF_AVAILABLE} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") + DECREASING_DIFFICULTY_IF_AVAILABLE, + DESCENDING, + DESCENDING_IF_AVAILABLE } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 7afbd80590..39a2873297 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlSeeAlso; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -82,7 +82,7 @@ public abstract class MoveSelectorConfig filterClass = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -128,11 +128,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -202,7 +202,7 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { } public @NonNull Config_ withSorterWeightFactoryClass( - @NonNull Class sorterWeightFactoryClass) { + @NonNull Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index ced152f717..923efbc1ce 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -7,7 +7,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -61,7 +61,7 @@ public class ValueSelectorConfig extends SelectorConfig { protected ValueSorterManner sorterManner = null; protected Class sorterComparatorClass = null; - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -162,11 +162,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -258,7 +258,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull ValueSelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } @@ -342,46 +342,30 @@ public String toString() { public static boolean hasSorter(@NonNull ValueSorterManner valueSorterManner, @NonNull GenuineVariableDescriptor variableDescriptor) { - switch (valueSorterManner) { - case NONE: - return false; - case INCREASING_STRENGTH: - case DECREASING_STRENGTH: - return true; - case INCREASING_STRENGTH_IF_AVAILABLE: - return variableDescriptor.getAscendingSorter() != null; - case DECREASING_STRENGTH_IF_AVAILABLE: - return variableDescriptor.getDescendingSorter() != null; - default: - throw new IllegalStateException("The sorterManner (" - + valueSorterManner + ") is not implemented."); - } + return switch (valueSorterManner) { + case NONE -> false; + case INCREASING_STRENGTH, DECREASING_STRENGTH, ASCENDING, DESCENDING -> true; + case INCREASING_STRENGTH_IF_AVAILABLE, ASCENDING_IF_AVAILABLE -> + variableDescriptor.getAscendingSorter() != null; + case DECREASING_STRENGTH_IF_AVAILABLE, DESCENDING_IF_AVAILABLE -> + variableDescriptor.getDescendingSorter() != null; + }; } public static @NonNull SelectionSorter determineSorter( @NonNull ValueSorterManner valueSorterManner, @NonNull GenuineVariableDescriptor variableDescriptor) { - SelectionSorter sorter; - switch (valueSorterManner) { - case NONE: - throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); - case INCREASING_STRENGTH: - case INCREASING_STRENGTH_IF_AVAILABLE: - sorter = variableDescriptor.getAscendingSorter(); - break; - case DECREASING_STRENGTH: - case DECREASING_STRENGTH_IF_AVAILABLE: - sorter = variableDescriptor.getDescendingSorter(); - break; - default: - throw new IllegalStateException("The sorterManner (" - + valueSorterManner + ") is not implemented."); - } + SelectionSorter sorter = switch (valueSorterManner) { + case NONE -> throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); + case INCREASING_STRENGTH, INCREASING_STRENGTH_IF_AVAILABLE, ASCENDING, ASCENDING_IF_AVAILABLE -> + variableDescriptor.getAscendingSorter(); + case DECREASING_STRENGTH, DECREASING_STRENGTH_IF_AVAILABLE, DESCENDING, DESCENDING_IF_AVAILABLE -> + variableDescriptor.getDescendingSorter(); + }; if (sorter == null) { - throw new IllegalArgumentException("The sorterManner (" + valueSorterManner - + ") on entity class (" + variableDescriptor.getEntityDescriptor().getEntityClass() - + ")'s variable (" + variableDescriptor.getVariableName() - + ") fails because that variable getter's @" + PlanningVariable.class.getSimpleName() - + " annotation does not declare any strength comparison."); + throw new IllegalArgumentException( + "The sorterManner (%s) on entity class (%s)'s variable (%s) fails because that variable getter's @%s annotation does not declare any strength comparison." + .formatted(valueSorterManner, variableDescriptor.getEntityDescriptor().getEntityClass(), + variableDescriptor.getVariableName(), PlanningVariable.class.getSimpleName())); } return sorter; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java index dfe5d79918..7702d36b74 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java @@ -11,10 +11,30 @@ @XmlEnum public enum ValueSorterManner { NONE(true), + /** + * @deprecated use {@link #ASCENDING} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") INCREASING_STRENGTH(false), + /** + * @deprecated use {@link #ASCENDING_IF_AVAILABLE} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") INCREASING_STRENGTH_IF_AVAILABLE(true), + /** + * @deprecated use {@link #DESCENDING} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") DECREASING_STRENGTH(false), - DECREASING_STRENGTH_IF_AVAILABLE(true); + /** + * @deprecated use {@link #DESCENDING_IF_AVAILABLE} instead + */ + @Deprecated(forRemoval = true, since = "1.28.0") + DECREASING_STRENGTH_IF_AVAILABLE(true), + ASCENDING(false), + ASCENDING_IF_AVAILABLE(true), + DESCENDING(false), + DESCENDING_IF_AVAILABLE(true); private final boolean nonePossible; 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 9406de5008..a2519a7af7 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 @@ -111,7 +111,7 @@ public class EntityDescriptor { // Only declared movable filter, excludes inherited and descending movable filters private MovableFilter declaredMovableEntityFilter; - private SelectionSorter decreasingDifficultySorter; + private SelectionSorter descendingSorter; // Only declared variable descriptors, excludes inherited variable descriptors private Map> declaredGenuineVariableDescriptorMap; @@ -243,7 +243,7 @@ private void processEntityAnnotations() { () -> new IllegalStateException("Impossible state as the previous if block would fail first.")); } processMovable(entityAnnotation); - processDifficulty(entityAnnotation); + processSorting(entityAnnotation); } /** @@ -263,35 +263,68 @@ private void processMovable(PlanningEntity entityAnnotation) { } } - private void processDifficulty(PlanningEntity entityAnnotation) { + private void processSorting(PlanningEntity entityAnnotation) { if (entityAnnotation == null) { return; } var difficultyComparatorClass = entityAnnotation.difficultyComparatorClass(); - if (difficultyComparatorClass == PlanningEntity.NullDifficultyComparator.class) { + if (difficultyComparatorClass != null + && PlanningEntity.NullComparator.class.isAssignableFrom(difficultyComparatorClass)) { difficultyComparatorClass = null; } + var comparatorClass = entityAnnotation.comparatorClass(); + if (comparatorClass != null + && PlanningEntity.NullComparator.class.isAssignableFrom(comparatorClass)) { + comparatorClass = null; + } + if (difficultyComparatorClass != null && comparatorClass != null) { + throw new IllegalStateException( + "The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(getEntityClass(), + "difficultyComparatorClass", difficultyComparatorClass.getName(), "comparatorClass", + comparatorClass.getName())); + } var difficultyWeightFactoryClass = entityAnnotation.difficultyWeightFactoryClass(); - if (difficultyWeightFactoryClass == PlanningEntity.NullDifficultyWeightFactory.class) { + if (difficultyWeightFactoryClass != null + && PlanningEntity.NullComparatorFactory.class.isAssignableFrom(difficultyWeightFactoryClass)) { difficultyWeightFactoryClass = null; } - if (difficultyComparatorClass != null && difficultyWeightFactoryClass != null) { - throw new IllegalStateException( - "The entityClass (%s) cannot have a difficultyComparatorClass (%s) and a difficultyWeightFactoryClass (%s) at the same time." - .formatted(entityClass, difficultyComparatorClass.getName(), - difficultyWeightFactoryClass.getName())); + var comparatorFactoryClass = entityAnnotation.comparatorFactoryClass(); + if (comparatorFactoryClass != null + && PlanningEntity.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) { + comparatorFactoryClass = null; } + if (difficultyWeightFactoryClass != null && comparatorFactoryClass != null) { + throw new IllegalStateException( + "The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(getEntityClass(), + "difficultyWeightFactoryClass", difficultyWeightFactoryClass.getName(), "comparatorFactoryClass", + comparatorFactoryClass.getName())); + } + // Selected settings + var selectedComparatorPropertyName = "comparatorClass"; + var selectedComparatorClass = comparatorClass; + var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; + var selectedComparatorFactoryClass = comparatorFactoryClass; if (difficultyComparatorClass != null) { - var difficultyComparator = ConfigUtils.newInstance(this::toString, - "difficultyComparatorClass", difficultyComparatorClass); - decreasingDifficultySorter = new ComparatorSelectionSorter<>( - difficultyComparator, SelectionSorterOrder.DESCENDING); + selectedComparatorPropertyName = "difficultyComparatorClass"; + selectedComparatorClass = difficultyComparatorClass; } if (difficultyWeightFactoryClass != null) { - var difficultyWeightFactory = ConfigUtils.newInstance(this::toString, - "difficultyWeightFactoryClass", difficultyWeightFactoryClass); - decreasingDifficultySorter = new FactorySelectionSorter<>( - difficultyWeightFactory, SelectionSorterOrder.DESCENDING); + selectedComparatorFactoryPropertyName = "difficultyWeightFactoryClass"; + selectedComparatorFactoryClass = difficultyWeightFactoryClass; + } + if (selectedComparatorClass != null && selectedComparatorFactoryClass != null) { + throw new IllegalStateException("The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time." + .formatted(entityClass, selectedComparatorPropertyName, selectedComparatorClass.getName(), + selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass.getName())); + } + if (selectedComparatorClass != null) { + var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorPropertyName, selectedComparatorClass); + descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); + } + if (selectedComparatorFactoryClass != null) { + var comparatorFactory = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, + selectedComparatorFactoryClass); + descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); } } @@ -643,8 +676,8 @@ public UniEnumeratingFilter getEntityMovablePredicate() { return (UniEnumeratingFilter) entityMovablePredicate; } - public SelectionSorter getDecreasingDifficultySorter() { - return decreasingDifficultySorter; + public SelectionSorter getDescendingSorter() { + return descendingSorter; } public Collection getGenuineVariableNameSet() { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java index 86af0a31fe..cd388b32be 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java @@ -2,7 +2,7 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; @@ -83,21 +83,22 @@ private SortingProperties assertSortingProperties(PlanningVariable planningVaria entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthWeightFactoryClass", strengthWeightFactoryClass.getName(), "comparatorFactoryClass", comparatorFactoryClass.getName())); } - // Final properties - var comparatorPropertyName = "comparatorClass"; - var comparatorPropertyClass = comparatorClass; - var factoryPropertyName = "comparatorFactoryClass"; - var factoryPropertyClass = comparatorFactoryClass; + // Selected settings + var selectedComparatorPropertyName = "comparatorClass"; + var selectedComparatorClass = comparatorClass; + var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; + var selectedComparatorFactoryClass = comparatorFactoryClass; if (strengthComparatorClass != null) { - comparatorPropertyName = "strengthComparatorClass"; - comparatorPropertyClass = strengthComparatorClass; + selectedComparatorPropertyName = "strengthComparatorClass"; + selectedComparatorClass = strengthComparatorClass; } if (strengthWeightFactoryClass != null) { - factoryPropertyName = "strengthWeightFactoryClass"; - factoryPropertyClass = strengthWeightFactoryClass; + selectedComparatorFactoryPropertyName = "strengthWeightFactoryClass"; + selectedComparatorFactoryClass = strengthWeightFactoryClass; } - return new SortingProperties(comparatorPropertyName, comparatorPropertyClass, factoryPropertyName, - factoryPropertyClass); + return new SortingProperties(selectedComparatorPropertyName, selectedComparatorClass, + selectedComparatorFactoryPropertyName, + selectedComparatorFactoryClass); } private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) { @@ -186,7 +187,7 @@ public SelectionFilter getMovableChainedTrailingValueFilter() } private record SortingProperties(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 6e6b1587ec..df7b9ad0bb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -8,7 +8,7 @@ import java.util.Comparator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; @@ -157,7 +157,7 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli @SuppressWarnings("rawtypes") protected void processSorting(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) { comparatorClass = null; } @@ -179,7 +179,7 @@ protected void processSorting(String comparatorPropertyName, Class comparatorFactory = + ComparatorFactory comparatorFactory = newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); ascendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index db03a7b737..402d04fd58 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -7,7 +7,7 @@ import java.util.SortedMap; import java.util.TreeMap; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; @@ -16,19 +16,19 @@ import ai.timefold.solver.core.impl.heuristic.selector.Selector; /** - * Sorts a selection {@link List} based on a {@link SorterFactory}. + * Sorts a selection {@link List} based on a {@link ComparatorFactory}. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ public final class FactorySelectionSorter implements SelectionSorter { - private final SorterFactory selectionSorterFactory; + private final ComparatorFactory selectionComparatorFactory; private final Comparator appliedComparator; - public FactorySelectionSorter(SorterFactory selectionSorterFactory, + public FactorySelectionSorter(ComparatorFactory selectionComparatorFactory, SelectionSorterOrder selectionSorterOrder) { - this.selectionSorterFactory = selectionSorterFactory; + this.selectionComparatorFactory = selectionComparatorFactory; switch (selectionSorterOrder) { case ASCENDING: this.appliedComparator = Comparator.naturalOrder(); @@ -55,7 +55,7 @@ public void sort(ScoreDirector scoreDirector, List selectionList) public void sort(Solution_ solution, List selectionList) { SortedMap selectionMap = new TreeMap<>(appliedComparator); for (T selection : selectionList) { - Comparable difficultyWeight = selectionSorterFactory.createSorter(solution, selection); + Comparable difficultyWeight = selectionComparatorFactory.createSorter(solution, selection); T previous = selectionMap.put(difficultyWeight, selection); if (previous != null) { throw new IllegalStateException("The selectionList contains 2 times the same selection (" @@ -73,12 +73,12 @@ public boolean equals(Object other) { if (other == null || getClass() != other.getClass()) return false; FactorySelectionSorter that = (FactorySelectionSorter) other; - return Objects.equals(selectionSorterFactory, that.selectionSorterFactory) + return Objects.equals(selectionComparatorFactory, that.selectionComparatorFactory) && Objects.equals(appliedComparator, that.appliedComparator); } @Override public int hashCode() { - return Objects.hash(selectionSorterFactory, appliedComparator); + return Objects.hash(selectionComparatorFactory, appliedComparator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index fb66b6a606..dca0bbc505 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.impl.heuristic.move.Move; @@ -16,13 +16,13 @@ * Implementations are expected to be stateless. * The solver may choose to reuse instances. * - * @deprecated Deprecated in favor of {@link SorterFactory}. + * @deprecated Deprecated in favor of {@link ComparatorFactory}. * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type */ @Deprecated(forRemoval = true, since = "1.28.0") -public interface SelectionSorterWeightFactory extends SorterFactory { +public interface SelectionSorterWeightFactory extends ComparatorFactory { Comparable createSorterWeight(Solution_ solution, T selection); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index b537bb1d9f..4c8f29d3a8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -6,7 +6,7 @@ import java.util.function.Function; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -316,9 +316,9 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SorterFactory sorterFactory = + ComparatorFactory comparatorFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new FactorySelectionSorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java index 137b941beb..a1f85efb32 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java @@ -41,8 +41,8 @@ public DestinationSelector buildDestinationSelector(HeuristicConfigPo var hasSortManner = configPolicy.getEntitySorterManner() != null && configPolicy.getEntitySorterManner() != NONE; var entityDescriptor = deduceEntityDescriptor(configPolicy, entitySelectorConfig.getEntityClass()); - var hasDifficultySorter = entityDescriptor.getDecreasingDifficultySorter() != null; - if (hasSortManner && hasDifficultySorter && entitySelectorConfig.getSorterManner() == null) { + var hasSorter = entityDescriptor.getDescendingSorter() != null; + if (hasSortManner && hasSorter && entitySelectorConfig.getSorterManner() == null) { if (entityValueRangeRecorderId == null) { // Solution-range model entitySelectorConfig.setCacheType(SelectionCacheType.PHASE); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 8ebc2cb5c0..799c603c8d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -2,7 +2,7 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; 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.common.decorator.SelectionSorterOrder; @@ -195,9 +195,9 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterWeightFactoryClass != null) { - SorterFactory> sorterFactory = + ComparatorFactory> comparatorFactory = ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); - sorter = new FactorySelectionSorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index e37ca61e03..1dbaf68d8b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.function.Function; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -336,9 +336,9 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterWeightFactoryClass() != null) { - SorterFactory sorterFactory = + ComparatorFactory comparatorFactory = instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); - sorter = new FactorySelectionSorter<>(sorterFactory, + sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index 9ebbdbd4c6..a4a6587946 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -1742,6 +1742,10 @@ + + + + @@ -1760,6 +1764,14 @@ + + + + + + + + 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 9f6221d05d..70af507530 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 @@ -77,10 +77,14 @@ import ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableSolution; import ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableEntity; import ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableSolution; -import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableEntity; -import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableSolution; -import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableEntity; -import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.entity.TestdataInvalidTwoEntityComparatorSortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.entity.TestdataInvalidTwoEntityComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.value.TestdataInvalidTwoValueComparatorSortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.value.TestdataInvalidTwoValueComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.entity.TestdataInvalidTwoEntityFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.entity.TestdataInvalidTwoEntityFactorySortableSolution; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.value.TestdataInvalidTwoValueFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.invalid.twofactory.value.TestdataInvalidTwoValueFactorySortableSolution; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEasyScoreCalculator; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.unassignedvar.TestdataAllowsUnassignedSolution; @@ -435,6 +439,19 @@ private static List generateCommonConfiguration new int[] { 0, 1, 2 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DESCENDING) + .withValueSorterManner(ValueSorterManner.DESCENDING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -446,6 +463,17 @@ private static List generateCommonConfiguration new int[] { 0, 1, 2 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DESCENDING_IF_AVAILABLE) + .withValueSorterManner(ValueSorterManner.DESCENDING_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -457,6 +485,17 @@ private static List generateCommonConfiguration new int[] { 2, 1, 0 }, // Only the values are sorted, and shuffling the entities will alter the expected result false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.DESCENDING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -468,6 +507,17 @@ private static List generateCommonConfiguration new int[] { 2, 1, 0 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DESCENDING) + .withValueSorterManner(ValueSorterManner.ASCENDING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -479,6 +529,17 @@ private static List generateCommonConfiguration new int[] { 2, 1, 0 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.DESCENDING_IF_AVAILABLE) + .withValueSorterManner(ValueSorterManner.ASCENDING_IF_AVAILABLE) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) @@ -490,6 +551,17 @@ private static List generateCommonConfiguration new int[] { 0, 1, 2 }, // Only the values are sorted, and shuffling the entities will alter the expected result false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withConstructionHeuristicType(ConstructionHeuristicType.ALLOCATE_TO_VALUE_FROM_QUEUE) + .withEntitySorterManner(EntitySorterManner.NONE) + .withValueSorterManner(ValueSorterManner.ASCENDING) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.CHEAPEST_INSERTION), @@ -539,6 +611,30 @@ private static List generateCommonConfiguration new int[] { 0, 1, 2 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DESCENDING)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.DESCENDING)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedEntityPlacerConfig() @@ -563,6 +659,30 @@ private static List generateCommonConfiguration new int[] { 2, 1, 0 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(EntitySorterManner.DESCENDING)) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(ValueSorterManner.ASCENDING)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from increasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); return values; } @@ -810,6 +930,31 @@ void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi new int[] { 0, 1, 2 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DESCENDING)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(EntitySorterManner.DESCENDING))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // Since we are starting from decreasing strength + // and the entities are being read in decreasing order of difficulty, + // this is expected: e1[1], e2[2], and e3[3] + new int[] { 0, 1, 2 }, + // Both are sorted and the expected result won't be affected + true)); var nonSortedEntityConfig = new EntitySelectorConfig(); var isPhaseScope = entityDestinationCacheType == SelectionCacheType.PHASE; if (isPhaseScope) { @@ -841,6 +986,27 @@ void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi isPhaseScope ? new int[] { 2, 1, 0 } : new int[] { 0, 1, 2 }, // Only the values are sorted, and shuffling the entities will alter the expected result false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DESCENDING)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(nonSortedEntityConfig)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[3], e2[2], and e3[1] + // The step scope will apply the default entity sort manner + isPhaseScope ? new int[] { 2, 1, 0 } : new int[] { 0, 1, 2 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -864,6 +1030,29 @@ void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi new int[] { 2, 1, 0 }, // Both are sorted and the expected result won't be affected true)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.ASCENDING)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(entityDestinationCacheType) + .withSorterManner(EntitySorterManner.DESCENDING))))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[3], e2[2], and e3[1] + new int[] { 2, 1, 0 }, + // Both are sorted and the expected result won't be affected + true)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() .withEntityPlacerConfig(new QueuedValuePlacerConfig() @@ -885,6 +1074,27 @@ void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi isPhaseScope ? new int[] { 0, 1, 2 } : new int[] { 2, 1, 0 }, // Only the values are sorted, and shuffling the entities will alter the expected result false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.ASCENDING)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig().withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(nonSortedEntityConfig)))) + .withForagerConfig(new ConstructionHeuristicForagerConfig().withPickEarlyType( + ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD)), + // this is expected: e1[1], e2[2], and e3[3] + // The step scope will apply the default entity sort manner + isPhaseScope ? new int[] { 0, 1, 2 } : new int[] { 2, 1, 0 }, + // Only the values are sorted, and shuffling the entities will alter the expected result + false)); return values; } @@ -1039,6 +1249,34 @@ void failConstructionHeuristicEntityRange() { assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) .hasMessageContaining("resolvedSelectionOrder (SORTED) which does not support the resolvedCacheType (PHASE)") .hasMessageContaining("Maybe set the \"cacheType\" to STEP."); + + var solverConfig2 = + PlannerTestUtils + .buildSolverConfig(TestdataListSortableEntityProvidingSolution.class, + TestdataListSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(ListOneValuePerEntityRangeEasyScoreCalculator.class) + .withPhases( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig() + .withId("sortedValueSelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner(ValueSorterManner.DESCENDING)) + .withMoveSelectorConfig(new ListChangeMoveSelectorConfig() + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withDestinationSelectorConfig(new DestinationSelectorConfig() + .withValueSelectorConfig(new ValueSelectorConfig()) + .withEntitySelectorConfig(new EntitySelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterManner( + EntitySorterManner.DESCENDING)))))); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig2, solution)) + .hasMessageContaining("resolvedSelectionOrder (SORTED) which does not support the resolvedCacheType (PHASE)") + .hasMessageContaining("Maybe set the \"cacheType\" to STEP."); } @Test @@ -1093,35 +1331,70 @@ void failConstructionHeuristicMixedProperties() { @Test void failConstructionHeuristicBothProperties() { - // Two comparator properties - var solverConfig = - PlannerTestUtils - .buildSolverConfig(TestdataInvalidTwoComparatorSortableSolution.class, - TestdataInvalidTwoComparatorSortableEntity.class) - .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); - var solution = new TestdataInvalidTwoComparatorSortableSolution(); - assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) - .hasMessageContaining( - "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.TestdataInvalidTwoComparatorSortableEntity) property (value)") - .hasMessageContaining( - "cannot have a strengthComparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") - .hasMessageContaining( - "nd a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator) at the same time."); - - // Comparator and Factory properties - var otherSolverConfig = - PlannerTestUtils - .buildSolverConfig(TestdataInvalidTwoFactorySortableSolution.class, - TestdataInvalidTwoFactorySortableEntity.class) - .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); - var otherSolution = new TestdataInvalidTwoFactorySortableSolution(); - assertThatCode(() -> PlannerTestUtils.solve(otherSolverConfig, otherSolution)) - .hasMessageContaining( - "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twofactory.TestdataInvalidTwoFactorySortableEntity) property (value)") - .hasMessageContaining( - "cannot have a strengthWeightFactoryClass (ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory)") - .hasMessageContaining( - "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + // Value + { + // Two comparator properties + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoValueComparatorSortableSolution.class, + TestdataInvalidTwoValueComparatorSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var solution = new TestdataInvalidTwoValueComparatorSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.value.TestdataInvalidTwoValueComparatorSortableEntity) property (value)") + .hasMessageContaining( + "cannot have a strengthComparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "and a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator) at the same time."); + + // Comparator and Factory properties + var otherSolverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoValueFactorySortableSolution.class, + TestdataInvalidTwoValueFactorySortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var otherSolution = new TestdataInvalidTwoValueFactorySortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(otherSolverConfig, otherSolution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twofactory.value.TestdataInvalidTwoValueFactorySortableEntity) property (value)") + .hasMessageContaining( + "cannot have a strengthWeightFactoryClass (ai.timefold.solver.core.testdomain.common.DummyWeightValueFactory)") + .hasMessageContaining( + "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + } + // Entity + { + // Two comparator properties + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoEntityComparatorSortableSolution.class, + TestdataInvalidTwoEntityComparatorSortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var solution = new TestdataInvalidTwoEntityComparatorSortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.entity.TestdataInvalidTwoEntityComparatorSortableEntity)") + .hasMessageContaining( + "cannot have a difficultyComparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + .hasMessageContaining( + "and a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator) at the same time."); + + // Comparator and Factory properties + var otherSolverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataInvalidTwoEntityFactorySortableSolution.class, + TestdataInvalidTwoEntityFactorySortableEntity.class) + .withEasyScoreCalculatorClass(DummyHardSoftEasyScoreCalculator.class); + var otherSolution = new TestdataInvalidTwoEntityFactorySortableSolution(); + assertThatCode(() -> PlannerTestUtils.solve(otherSolverConfig, otherSolution)) + .hasMessageContaining( + "The entityClass (class ai.timefold.solver.core.testdomain.sort.invalid.twofactory.entity.TestdataInvalidTwoEntityFactorySortableEntity)") + .hasMessageContaining( + "cannot have a difficultyWeightFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory)") + .hasMessageContaining( + "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + } } @Test diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java index 312dd041ae..eea4e25c6d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import java.util.List; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import ai.timefold.solver.core.testdomain.TestdataEntity; @@ -18,7 +18,7 @@ class FactorySelectionSorterTest { @Test void sortAscending() { - SorterFactory weightFactory = (solution, selection) -> Integer + ComparatorFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.ASCENDING); @@ -34,7 +34,7 @@ void sortAscending() { @Test void sortDescending() { - SorterFactory weightFactory = (solution, selection) -> Integer + ComparatorFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.DESCENDING); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index a8c412d730..71af79886b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -7,7 +7,7 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -156,7 +156,7 @@ void applySorting_withSorterComparatorClass() { @Test void applySorting_withSorterWeightFactoryClass() { EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() - .withSorterWeightFactoryClass(DummySelectionSorterFactory.class); + .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class); applySorting(entitySelectorConfig); } @@ -207,8 +207,8 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionSorterFactory - implements SorterFactory { + public static class DummySelectionComparatorFactory + implements ComparatorFactory { @Override public Comparable createSorter(TestdataSolution testdataSolution, TestdataEntity selection) { return 0; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 1939608068..d214f6fcd8 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -11,7 +11,7 @@ import java.util.Iterator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -218,7 +218,7 @@ void applySorting_withSorterComparatorClass() { @Test void applySorting_withSorterWeightFactoryClass() { ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() - .withSorterWeightFactoryClass(DummySelectionSorterFactory.class); + .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class); applySorting(valueSelectorConfig); } @@ -310,8 +310,8 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionSorterFactory - implements SorterFactory { + public static class DummySelectionComparatorFactory + implements ComparatorFactory { @Override public Comparable createSorter(TestdataSolution testdataSolution, TestdataValue selection) { return 0; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java index 08390ba34e..fd938bed37 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java @@ -1,11 +1,11 @@ package ai.timefold.solver.core.testdomain.common; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; public class DummyValueFactory - implements SorterFactory { + implements ComparatorFactory { @Override public Comparable createSorter(TestdataListFactorySortableSolution solution, TestdataValue selection) { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java index 8b86d8b70b..fe0fbe5336 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java @@ -1,10 +1,10 @@ package ai.timefold.solver.core.testdomain.common; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; public class TestSortableFactory - implements SelectionSorterWeightFactory, SorterFactory { + implements SelectionSorterWeightFactory, ComparatorFactory { @Override public Comparable createSorterWeight(Object o, TestSortableObject selection) { return selection; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java index 1dcf32571b..0f1e51f720 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.testdomain.difficultyweight; -import ai.timefold.solver.core.api.domain.common.SorterFactory; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; public class TestdataDifficultyFactory implements - SorterFactory { + ComparatorFactory { @Override public TestdataDifficultyWeightComparable createSorter(TestdataDifficultyWeightSolution solution, diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java index 7ebde4617c..a1120e2dbd 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.testdomain.common.TestSortableObject; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) +@PlanningEntity(comparatorClass = TestSortableComparator.class) public class TestdataNewSortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java index 835ffc0430..47caf8a3a5 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.testdomain.common.TestSortableObject; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) +@PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) public class TestdataFactoryNewSortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableEntity.java new file mode 100644 index 0000000000..0258960ace --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableEntity.java @@ -0,0 +1,39 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.entity; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(comparatorClass = DummyValueComparator.class, difficultyComparatorClass = DummyValueComparator.class) +public class TestdataInvalidTwoEntityComparatorSortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataInvalidTwoEntityComparatorSortableEntity() { + } + + public TestdataInvalidTwoEntityComparatorSortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableSolution.java new file mode 100644 index 0000000000..98b0f366da --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/entity/TestdataInvalidTwoEntityComparatorSortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.entity; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidTwoEntityComparatorSortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidTwoEntityComparatorSortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableEntity.java similarity index 80% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableEntity.java index c7906950fb..bdbbaa19bd 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator; +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.value; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -7,17 +7,17 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity -public class TestdataInvalidTwoComparatorSortableEntity extends TestdataObject { +public class TestdataInvalidTwoValueComparatorSortableEntity extends TestdataObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, strengthComparatorClass = DummyValueComparator.class) private TestdataSortableValue value; private int difficulty; - public TestdataInvalidTwoComparatorSortableEntity() { + public TestdataInvalidTwoValueComparatorSortableEntity() { } - public TestdataInvalidTwoComparatorSortableEntity(String code, int difficulty) { + public TestdataInvalidTwoValueComparatorSortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableSolution.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableSolution.java index 897bb02d3f..c2eaadd32e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/TestdataInvalidTwoComparatorSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twocomparator/value/TestdataInvalidTwoValueComparatorSortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator; +package ai.timefold.solver.core.testdomain.sort.invalid.twocomparator.value; import java.util.List; @@ -10,10 +10,10 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataInvalidTwoComparatorSortableSolution { +public class TestdataInvalidTwoValueComparatorSortableSolution { private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -27,11 +27,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -44,7 +44,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataInvalidTwoComparatorSortableEntity entity) { + public void removeEntity(TestdataInvalidTwoValueComparatorSortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableEntity.java new file mode 100644 index 0000000000..5c37dff2c7 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableEntity.java @@ -0,0 +1,39 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory.entity; + +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; +import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningEntity(comparatorFactoryClass = DummyValueFactory.class, difficultyWeightFactoryClass = DummyValueFactory.class) +public class TestdataInvalidTwoEntityFactorySortableEntity extends TestdataObject { + + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = DummyValueFactory.class) + private TestdataSortableValue value; + private int difficulty; + + public TestdataInvalidTwoEntityFactorySortableEntity() { + } + + public TestdataInvalidTwoEntityFactorySortableEntity(String code, int difficulty) { + super(code); + this.difficulty = difficulty; + } + + public TestdataSortableValue getValue() { + return value; + } + + public void setValue(TestdataSortableValue value) { + this.value = value; + } + + 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/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableSolution.java new file mode 100644 index 0000000000..8399a12602 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/entity/TestdataInvalidTwoEntityFactorySortableSolution.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory.entity; + +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.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; + +@PlanningSolution +public class TestdataInvalidTwoEntityFactorySortableSolution { + + private List valueList; + private List entityList; + private HardSoftScore score; + + @ValueRangeProvider(id = "valueRange") + @PlanningEntityCollectionProperty + public List getValueList() { + return valueList; + } + + public void setValueList(List valueList) { + this.valueList = valueList; + } + + @PlanningEntityCollectionProperty + public List getEntityList() { + return entityList; + } + + public void setEntityList(List entityList) { + this.entityList = entityList; + } + + @PlanningScore + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + public void removeEntity(TestdataInvalidTwoEntityFactorySortableEntity entity) { + this.entityList = entityList.stream() + .filter(e -> e != entity) + .toList(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableEntity.java similarity index 82% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableEntity.java index 399fb78ac3..d7592e526c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.invalid.twofactory; +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory.value; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -8,17 +8,17 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity -public class TestdataInvalidTwoFactorySortableEntity extends TestdataObject { +public class TestdataInvalidTwoValueFactorySortableEntity extends TestdataObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = DummyValueFactory.class, strengthWeightFactoryClass = DummyWeightValueFactory.class) private TestdataSortableValue value; private int difficulty; - public TestdataInvalidTwoFactorySortableEntity() { + public TestdataInvalidTwoValueFactorySortableEntity() { } - public TestdataInvalidTwoFactorySortableEntity(String code, int difficulty) { + public TestdataInvalidTwoValueFactorySortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableSolution.java similarity index 76% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableSolution.java index a3f5d71c06..91114015a3 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/TestdataInvalidTwoFactorySortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/twofactory/value/TestdataInvalidTwoValueFactorySortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.invalid.twofactory; +package ai.timefold.solver.core.testdomain.sort.invalid.twofactory.value; import java.util.List; @@ -10,10 +10,10 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataInvalidTwoFactorySortableSolution { +public class TestdataInvalidTwoValueFactorySortableSolution { private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -27,11 +27,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -44,7 +44,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataInvalidTwoFactorySortableEntity entity) { + public void removeEntity(TestdataInvalidTwoValueFactorySortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index b0ba421a2f..a0b2ef41e9 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1599,14 +1599,14 @@ The solver may choose to reuse them in different contexts. ==== -[#sortedSelectionBySelectionSorterWeightFactory] -===== Sorted selection by `SorterFactory` +[#sortedSelectionByComparatorFactory] +===== Sorted selection by `ComparatorFactory` -If you need the entire solution to sort a ``Selector``, use a `SorterFactory` instead: +If you need the entire solution to sort a ``Selector``, use a `ComparatorFactory` instead: [source,java,options="nowrap"] ---- -public interface SorterFactory { +public interface ComparatorFactory { Comparable createSorter(Solution_ solution, T selection); @@ -1627,7 +1627,7 @@ You'll also need to configure it (unless it's annotated on the domain model and [NOTE] ==== -`SorterFactory` implementations are expected to be stateless. +`ComparatorFactory` implementations are expected to be stateless. The solver may choose to reuse them in different contexts. ==== diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index 4e60410ca0..0e07a9ea49 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -40,7 +40,7 @@ recipeList: newMethodName: createSorter - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.SorterFactory + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.ComparatorFactory ignoreDefinition: true - org.openrewrite.java.RemoveUnusedImports - ai.timefold.solver.migration.ChangeVersion From de069a67c675f9027b2b7c8cf84c1dda9d4dd6c1 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 14 Oct 2025 13:25:18 -0300 Subject: [PATCH 16/33] chore: address sonar --- .../solver/core/api/domain/entity/PlanningEntity.java | 6 +++--- .../core/api/domain/variable/PlanningVariable.java | 6 +++--- .../common/decorator/FactorySelectionSorter.java | 10 +++++----- .../common/decorator/SelectionSorterWeightFactory.java | 3 +++ .../DefaultConstructionHeuristicPhaseTest.java | 2 +- .../NewOneValuePerEntityEasyScoreCalculator.java | 4 +--- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index ddb90a46ff..57e58067cf 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -93,10 +93,10 @@ interface NullPinningFilter extends PinningFilter { Class comparatorClass() default NullComparator.class; /** Workaround for annotation limitation in {@link #difficultyComparatorClass()}. */ - interface NullDifficultyComparator extends NullComparator { + interface NullDifficultyComparator extends NullComparator { } - interface NullComparator extends Comparator { + interface NullComparator extends Comparator { } /** @@ -123,7 +123,7 @@ interface NullComparator extends Comparator { Class comparatorFactoryClass() default NullComparatorFactory.class; /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */ - interface NullDifficultyWeightFactory extends NullComparatorFactory { + interface NullDifficultyWeightFactory extends NullComparatorFactory { } interface NullComparatorFactory extends ComparatorFactory { diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 2484782321..9d3e81ccad 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -102,10 +102,10 @@ Class comparatorClass() default NullComparator.class; /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ - interface NullStrengthComparator extends NullComparator { + interface NullStrengthComparator extends NullComparator { } - interface NullComparator extends Comparator { + interface NullComparator extends Comparator { } /** @@ -132,7 +132,7 @@ interface NullComparator extends Comparator { Class comparatorFactoryClass() default NullComparatorFactory.class; /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ - interface NullStrengthWeightFactory extends NullComparatorFactory { + interface NullStrengthWeightFactory extends NullComparatorFactory { } interface NullComparatorFactory extends ComparatorFactory { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index 402d04fd58..96779e33f5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -53,10 +53,10 @@ public void sort(ScoreDirector scoreDirector, List selectionList) * of {@link PlanningEntity}, planningValue, {@link Move} or {@link Selector} */ public void sort(Solution_ solution, List selectionList) { - SortedMap selectionMap = new TreeMap<>(appliedComparator); - for (T selection : selectionList) { - Comparable difficultyWeight = selectionComparatorFactory.createSorter(solution, selection); - T previous = selectionMap.put(difficultyWeight, selection); + SortedMap, T> selectionMap = new TreeMap<>(appliedComparator); + for (var selection : selectionList) { + var difficultyWeight = selectionComparatorFactory.createSorter(solution, selection); + var previous = selectionMap.put(difficultyWeight, selection); if (previous != null) { throw new IllegalStateException("The selectionList contains 2 times the same selection (" + previous + ") and (" + selection + ")."); @@ -72,7 +72,7 @@ public boolean equals(Object other) { return true; if (other == null || getClass() != other.getClass()) return false; - FactorySelectionSorter that = (FactorySelectionSorter) other; + var that = (FactorySelectionSorter) other; return Objects.equals(selectionComparatorFactory, that.selectionComparatorFactory) && Objects.equals(appliedComparator, that.appliedComparator); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index dca0bbc505..c82fda6972 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -6,6 +6,8 @@ import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; +import org.jspecify.annotations.NullMarked; + /** * Creates a weight to decide the order of a collections of selections * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). @@ -22,6 +24,7 @@ * @param the selection type */ @Deprecated(forRemoval = true, since = "1.28.0") +@NullMarked public interface SelectionSorterWeightFactory extends ComparatorFactory { Comparable createSorterWeight(Solution_ solution, T selection); 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 70af507530..34460721f4 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 @@ -61,7 +61,7 @@ import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedSolution; -import ai.timefold.solver.core.testdomain.sort.comparator.NewOneValuePerEntityEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.NewOneValuePerEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableEntity; import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableSolution; import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.OldOneValuePerEntityEasyScoreCalculator; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java index 7afc6738e0..8f4905138a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java @@ -1,11 +1,9 @@ -package ai.timefold.solver.core.testdomain.sort.comparator; +package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; import java.util.Objects; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableEntity; -import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableSolution; import org.jspecify.annotations.NonNull; From 7a0469c8243175c4955cf4d9fefd6ee498466eb9 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 20 Oct 2025 08:40:07 -0300 Subject: [PATCH 17/33] chore: add new factory class setting to entity config --- benchmark/src/main/resources/benchmark.xsd | 3 + core/src/build/revapi-differences.json | 11 ++ .../selector/entity/EntitySelectorConfig.java | 37 ++++++ .../decorator/FactorySelectionSorter.java | 12 +- .../entity/EntitySelectorFactory.java | 56 +++++++-- core/src/main/resources/solver.xsd | 2 + ...DefaultConstructionHeuristicPhaseTest.java | 112 ++++++++++++++++++ .../entity/EntitySelectorFactoryTest.java | 31 +++-- 8 files changed, 237 insertions(+), 27 deletions(-) diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index a0af3558af..ac8a17f83c 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -893,6 +893,9 @@ + + + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index 3f13b3d264..ad6a05899c 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -393,6 +393,17 @@ "new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class===)", "parameterIndex": "0", "justification": "New comparator factory class" + }, + { + "ignore": true, + "code": "java.annotation.attributeValueChanged", + "old": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "new": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "annotationType": "jakarta.xml.bind.annotation.XmlType", + "attribute": "propOrder", + "oldValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "newValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "justification": "New comparator factory field" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index dd1deb03e0..ff8c198dc1 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -34,6 +34,7 @@ "sorterManner", "sorterComparatorClass", "sorterWeightFactoryClass", + "sorterComparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -63,7 +64,12 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe protected EntitySorterManner sorterManner = null; protected Class sorterComparatorClass = null; + /** + * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; + protected Class sorterComparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -156,14 +162,31 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } + /** + * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @param sorterWeightFactoryClass the class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } + public Class getSorterComparatorFactoryClass() { + return sorterComparatorFactoryClass; + } + + public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { + this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + } + public @Nullable SelectionSorterOrder getSorterOrder() { return sorterOrder; } @@ -246,12 +269,23 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { return this; } + /** + * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @param weightFactoryClass the factory class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull EntitySelectorConfig withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } + public @NonNull EntitySelectorConfig + withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setSorterComparatorFactoryClass(comparatorFactoryClass); + return this; + } + public @NonNull EntitySelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { this.setSorterOrder(sorterOrder); return this; @@ -295,6 +329,8 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); + sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( @@ -320,6 +356,7 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) { classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); classVisitor.accept(sorterWeightFactoryClass); + classVisitor.accept(sorterComparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index 96779e33f5..15215b4b48 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -37,8 +37,8 @@ public FactorySelectionSorter(ComparatorFactory selectionComparato this.appliedComparator = Collections.reverseOrder(); break; default: - throw new IllegalStateException("The selectionSorterOrder (" + selectionSorterOrder - + ") is not implemented."); + throw new IllegalStateException( + "The selectionSorterOrder (%s) is not implemented.".formatted(selectionSorterOrder)); } } @@ -55,11 +55,11 @@ public void sort(ScoreDirector scoreDirector, List selectionList) public void sort(Solution_ solution, List selectionList) { SortedMap, T> selectionMap = new TreeMap<>(appliedComparator); for (var selection : selectionList) { - var difficultyWeight = selectionComparatorFactory.createSorter(solution, selection); - var previous = selectionMap.put(difficultyWeight, selection); + var selectionSorter = selectionComparatorFactory.createSorter(solution, selection); + var previous = selectionMap.put(selectionSorter, selection); if (previous != null) { - throw new IllegalStateException("The selectionList contains 2 times the same selection (" - + previous + ") and (" + selection + ")."); + throw new IllegalStateException( + "The selectionList contains 2 times the same selection (%s) and (%s).".formatted(previous, selection)); } } selectionList.clear(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 4c8f29d3a8..fc21e91079 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -142,7 +142,8 @@ protected EntitySelector buildMimicReplaying(HeuristicConfigPolicy + determineSorterComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { + var propertyName = determineSorterComparatorFactoryPropertyName(entitySelectorConfig); + if (propertyName.equals("sorterWeightFactoryClass")) { + return entitySelectorConfig.getSorterWeightFactoryClass(); + } else { + return entitySelectorConfig.getSorterComparatorFactoryClass(); + } + } + private EntitySelector buildBaseEntitySelector(EntityDescriptor entityDescriptor, SelectionCacheType minimumCacheType, boolean randomSelection) { if (minimumCacheType == SelectionCacheType.SOLVER) { @@ -252,29 +275,33 @@ private EntitySelector applyFiltering(EntitySelector entit protected void validateSorting(SelectionOrder resolvedSelectionOrder) { var sorterManner = config.getSorterManner(); var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterWeightFactoryClass = config.getSorterWeightFactoryClass(); + var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); + var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); var sorterOrder = config.getSorterOrder(); var sorterClass = config.getSorterClass(); - if ((sorterManner != null || sorterComparatorClass != null || sorterWeightFactoryClass != null || sorterOrder != null + if ((sorterManner != null || sorterComparatorClass != null || sorterComparatorFactoryClass != null + || sorterOrder != null || sorterClass != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { throw new IllegalArgumentException(""" The entitySelectorConfig (%s) with sorterManner (%s) \ and sorterComparatorClass (%s) and sorterWeightFactoryClass (%s) and sorterOrder (%s) and sorterClass (%s) \ has a resolvedSelectionOrder (%s) that is not %s.""" - .formatted(config, sorterManner, sorterComparatorClass, sorterWeightFactoryClass, sorterOrder, sorterClass, + .formatted(config, sorterManner, sorterComparatorClass, sorterComparatorFactoryClass, sorterOrder, + sorterClass, resolvedSelectionOrder, SelectionOrder.SORTED)); } assertNotSorterMannerAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, "sorterWeightFactoryClass", EntitySelectorConfig::getSorterWeightFactoryClass); + assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", EntitySelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); assertNotSorterClassAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, "sorterWeightFactoryClass", EntitySelectorConfig::getSorterWeightFactoryClass); + assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); - if (sorterComparatorClass != null && sorterWeightFactoryClass != null) { + if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { throw new IllegalArgumentException( - "The entitySelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterWeightFactoryClass (%s)." - .formatted(config, sorterComparatorClass, sorterWeightFactoryClass)); + "The entitySelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s)." + .formatted(config, sorterComparatorClass, sorterComparatorFactoryPropertyName, + sorterComparatorFactoryClass)); } } @@ -304,6 +331,7 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; var sorterManner = config.getSorterManner(); + var comparatorFactoryClass = determineSorterComparatorFactoryClass(config); if (sorterManner != null) { var entityDescriptor = entitySelector.getEntityDescriptor(); if (!EntitySelectorConfig.hasSorter(sorterManner, entityDescriptor)) { @@ -315,20 +343,22 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (config.getSorterWeightFactoryClass() != null) { + } else if (comparatorFactoryClass != null) { + var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); + instanceCache.newInstance(config, comparatorFactoryPropertyName, comparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { + var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); throw new IllegalArgumentException(""" The entitySelectorConfig (%s) with resolvedSelectionOrder (%s) needs \ - a sorterManner (%s) or a sorterComparatorClass (%s) or a sorterWeightFactoryClass (%s) \ + a sorterManner (%s) or a sorterComparatorClass (%s) or a %s (%s) \ or a sorterClass (%s).""" .formatted(config, resolvedSelectionOrder, sorterManner, config.getSorterComparatorClass(), - config.getSorterWeightFactoryClass(), config.getSorterClass())); + comparatorFactoryPropertyName, comparatorFactoryClass, config.getSorterClass())); } entitySelector = new SortingEntitySelector<>(entitySelector, resolvedCacheType, sorter); } diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index a4a6587946..cd23b612ca 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -401,6 +401,8 @@ + + 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 34460721f4..5d8e122398 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 @@ -11,7 +11,9 @@ import java.util.Collections; import java.util.List; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicForagerConfig; @@ -27,10 +29,15 @@ 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.heuristic.selector.value.ValueSorterManner; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; +import ai.timefold.solver.core.testdomain.TestdataObject; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.common.DummyHardSoftEasyScoreCalculator; +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.sort.comparator.ListOneValuePerEntityEasyScoreCalculator; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableEntity; import ai.timefold.solver.core.testdomain.list.sort.comparator.TestdataListSortableSolution; @@ -105,6 +112,7 @@ import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.TestdataFactoryOldSortableEntityProvidingSolution; import ai.timefold.solver.core.testutil.PlannerTestUtils; +import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -1219,6 +1227,72 @@ void solveListVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig ph } } + private static List generateEntityFactorySortingConfiguration() { + var values = new ArrayList(); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withId("sortedValueSelector")) + .withMoveSelectorConfig(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterWeightFactoryClass(TestdataObjectSortableFactory.class)) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withValueSelectorConfig(new ValueSelectorConfig()))), + new int[] { 2, 1, 0 }, + // Only entities are sorted + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withId("sortedValueSelector")) + .withMoveSelectorConfig(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterComparatorFactoryClass(TestdataObjectSortableFactory.class)) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withValueSelectorConfig(new ValueSelectorConfig()))), + new int[] { 2, 1, 0 }, + // Only entities are sorted + false)); + return values; + } + + @ParameterizedTest + @MethodSource("generateEntityFactorySortingConfiguration") + void solveEntityFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class) + .withEasyScoreCalculatorClass(TestdataListSolutionEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataListSolution.generateUninitializedSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValueList()).hasSize(1); + assertThat(TestdataObjectSortableFactory.extractCode(entity.getValueList().get(0).getCode())) + .isEqualTo(phaseConfig.expected[i]); + } + } + } + @Test void failConstructionHeuristicEntityRange() { var solverConfig = @@ -1408,6 +1482,44 @@ void failMixedModelDefaultConfiguration() { "has both basic and list variables and cannot be deduced automatically"); } + public static class TestdataListSolutionEasyScoreCalculator + implements EasyScoreCalculator { + + @Override + public @NonNull SimpleScore calculateScore(@NonNull TestdataListSolution solution) { + var score = 0; + for (var entity : solution.getEntityList()) { + if (entity.getValueList().size() <= 1) { + score -= 1; + } else { + score -= 10; + } + score--; + } + return SimpleScore.of(score); + } + } + + public static class TestdataObjectSortableFactory + implements SelectionSorterWeightFactory, + ComparatorFactory { + + @Override + public Comparable createSorterWeight(TestdataListSolution solution, TestdataObject selection) { + return createSorter(solution, selection); + } + + @Override + public Comparable createSorter(TestdataListSolution solution, TestdataObject selection) { + return -extractCode(selection.getCode()); + } + + public static int extractCode(String code) { + var idx = code.lastIndexOf(" "); + return Integer.parseInt(code.substring(idx + 1)); + } + } + private record ConstructionHeuristicTestConfig(ConstructionHeuristicPhaseConfig config, int[] expected, boolean shuffle) { } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 71af79886b..cc8e27cd0b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -12,6 +12,7 @@ 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.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; @@ -34,8 +35,7 @@ void phaseOriginal() { EntitySelector entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM); assertThat(entitySelector) - .isInstanceOf(FromSolutionEntitySelector.class); - assertThat(entitySelector) + .isInstanceOf(FromSolutionEntitySelector.class) .isNotInstanceOf(ShufflingEntitySelector.class); assertThat(entitySelector.getCacheType()).isEqualTo(SelectionCacheType.PHASE); } @@ -48,8 +48,7 @@ void stepOriginal() { EntitySelector entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM); assertThat(entitySelector) - .isInstanceOf(FromSolutionEntitySelector.class); - assertThat(entitySelector) + .isInstanceOf(FromSolutionEntitySelector.class) .isNotInstanceOf(ShufflingEntitySelector.class); assertThat(entitySelector.getCacheType()).isEqualTo(SelectionCacheType.STEP); } @@ -75,8 +74,7 @@ void phaseRandom() { EntitySelector entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM); assertThat(entitySelector) - .isInstanceOf(FromSolutionEntitySelector.class); - assertThat(entitySelector) + .isInstanceOf(FromSolutionEntitySelector.class) .isNotInstanceOf(ShufflingEntitySelector.class); assertThat(entitySelector.getCacheType()).isEqualTo(SelectionCacheType.PHASE); } @@ -89,8 +87,7 @@ void stepRandom() { EntitySelector entitySelector = EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM); assertThat(entitySelector) - .isInstanceOf(FromSolutionEntitySelector.class); - assertThat(entitySelector) + .isInstanceOf(FromSolutionEntitySelector.class) .isNotInstanceOf(ShufflingEntitySelector.class); assertThat(entitySelector.getCacheType()).isEqualTo(SelectionCacheType.STEP); } @@ -198,6 +195,24 @@ void failFast_ifMimicRecordingIsUsedWithOtherProperty() { .withMessageContaining("has another property"); } + @Test + void failFast_ifBothFactoriesUsed() { + EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() + .withSorterManner(EntitySorterManner.DESCENDING) + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class) + .withSorterComparatorFactoryClass(DummySelectionComparatorFactory.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> EntitySelectorFactory. create(entitySelectorConfig) + .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining( + "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory)") + .withMessageContaining( + "and sorterComparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); + } + public static class DummySelectionProbabilityWeightFactory implements SelectionProbabilityWeightFactory { From db55b0ddddde0661f8a2928610aa1d4e3173f187 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 20 Oct 2025 10:21:37 -0300 Subject: [PATCH 18/33] chore: add new factory class setting to value config --- benchmark/src/main/resources/benchmark.xsd | 3 + core/src/build/revapi-differences.json | 11 +++ .../selector/value/ValueSelectorConfig.java | 37 +++++++ .../entity/EntitySelectorFactory.java | 2 +- .../selector/value/ValueSelectorFactory.java | 60 +++++++++--- core/src/main/resources/solver.xsd | 2 + ...DefaultConstructionHeuristicPhaseTest.java | 97 +++++++++++++++---- .../entity/EntitySelectorFactoryTest.java | 2 +- .../value/ValueSelectorFactoryTest.java | 20 ++++ .../common/TestdataObjectSortableFactory.java | 24 +++++ 10 files changed, 222 insertions(+), 36 deletions(-) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index ac8a17f83c..93b89abc92 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -1088,6 +1088,9 @@ + + + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index ad6a05899c..1c8e7fb9cd 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -404,6 +404,17 @@ "oldValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "newValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "justification": "New comparator factory field" + }, + { + "ignore": true, + "code": "java.annotation.attributeValueChanged", + "old": "class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig", + "new": "class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig", + "annotationType": "jakarta.xml.bind.annotation.XmlType", + "attribute": "propOrder", + "oldValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "newValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "justification": "New comparator factory field" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index 923efbc1ce..d999476c2e 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -35,6 +35,7 @@ "sorterManner", "sorterComparatorClass", "sorterWeightFactoryClass", + "sorterComparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -61,7 +62,12 @@ public class ValueSelectorConfig extends SelectorConfig { protected ValueSorterManner sorterManner = null; protected Class sorterComparatorClass = null; + /** + * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; + protected Class sorterComparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -162,14 +168,31 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } + /** + * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @param sorterWeightFactoryClass the class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } + public Class getSorterComparatorFactoryClass() { + return sorterComparatorFactoryClass; + } + + public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { + this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + } + public @Nullable SelectionSorterOrder getSorterOrder() { return sorterOrder; } @@ -257,12 +280,23 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { return this; } + /** + * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @param weightFactoryClass the factory class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull ValueSelectorConfig withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } + public @NonNull ValueSelectorConfig + withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setSorterComparatorFactoryClass(comparatorFactoryClass); + return this; + } + public @NonNull ValueSelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { this.setSorterOrder(sorterOrder); return this; @@ -306,6 +340,8 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); + sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( @@ -331,6 +367,7 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) { classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); classVisitor.accept(sorterWeightFactoryClass); + classVisitor.accept(sorterComparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index fc21e91079..5c277c782d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -184,7 +184,7 @@ protected boolean isBaseInherentlyCached() { return true; } - private String determineSorterComparatorFactoryPropertyName(EntitySelectorConfig entitySelectorConfig) { + private static String determineSorterComparatorFactoryPropertyName(EntitySelectorConfig entitySelectorConfig) { var weightFactoryClass = entitySelectorConfig.getSorterWeightFactoryClass(); var comparatorFactoryClass = entitySelectorConfig.getSorterComparatorFactoryClass(); if (weightFactoryClass != null && comparatorFactoryClass != null) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 1dbaf68d8b..066898b86c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -160,7 +160,7 @@ protected ValueSelector buildMimicReplaying(HeuristicConfigPolicy + determineSorterComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { + var propertyName = determineSorterComparatorFactoryPropertyName(valueSelectorConfig); + if (propertyName.equals("sorterWeightFactoryClass")) { + return valueSelectorConfig.getSorterWeightFactoryClass(); + } else { + return valueSelectorConfig.getSorterComparatorFactoryClass(); + } + } + private ValueSelector buildBaseValueSelector(GenuineVariableDescriptor variableDescriptor, SelectionCacheType minimumCacheType, boolean randomSelection) { var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor(); @@ -272,29 +294,32 @@ protected ValueSelector applyInitializedChainedValueFilter(HeuristicC protected void validateSorting(SelectionOrder resolvedSelectionOrder) { var sorterManner = config.getSorterManner(); var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterWeightFactoryClass = config.getSorterWeightFactoryClass(); + var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); + var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); var sorterOrder = config.getSorterOrder(); var sorterClass = config.getSorterClass(); - if ((sorterManner != null || sorterComparatorClass != null || sorterWeightFactoryClass != null || sorterOrder != null - || sorterClass != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { + if ((sorterManner != null || sorterComparatorClass != null || sorterComparatorFactoryClass != null + || sorterOrder != null || sorterClass != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { throw new IllegalArgumentException(""" The valueSelectorConfig (%s) with sorterManner (%s) \ - and sorterComparatorClass (%s) and sorterWeightFactoryClass (%s) and sorterOrder (%s) and sorterClass (%s) \ + and sorterComparatorClass (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) \ has a resolvedSelectionOrder (%s) that is not %s.""" - .formatted(config, sorterManner, sorterComparatorClass, sorterWeightFactoryClass, sorterOrder, sorterClass, - resolvedSelectionOrder, SelectionOrder.SORTED)); + .formatted(config, sorterManner, sorterComparatorClass, sorterComparatorFactoryPropertyName, + sorterComparatorFactoryClass, sorterOrder, sorterClass, resolvedSelectionOrder, + SelectionOrder.SORTED)); } assertNotSorterMannerAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, "sorterWeightFactoryClass", ValueSelectorConfig::getSorterWeightFactoryClass); + assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", ValueSelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); assertNotSorterClassAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, "sorterWeightFactoryClass", ValueSelectorConfig::getSorterWeightFactoryClass); + assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); - if (sorterComparatorClass != null && sorterWeightFactoryClass != null) { + if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { throw new IllegalArgumentException( - "The valueSelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterWeightFactoryClass (%s)." - .formatted(config, sorterComparatorClass, sorterWeightFactoryClass)); + "The valueSelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s)." + .formatted(config, sorterComparatorClass, sorterComparatorFactoryPropertyName, + sorterComparatorFactoryClass)); } } @@ -324,6 +349,7 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; var sorterManner = config.getSorterManner(); + var comparatorFactoryClass = determineSorterComparatorFactoryClass(config); if (sorterManner != null) { var variableDescriptor = valueSelector.getVariableDescriptor(); if (!ValueSelectorConfig.hasSorter(sorterManner, variableDescriptor)) { @@ -335,20 +361,22 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (config.getSorterWeightFactoryClass() != null) { + } else if (comparatorFactoryClass != null) { + var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, "sorterWeightFactoryClass", config.getSorterWeightFactoryClass()); + instanceCache.newInstance(config, comparatorFactoryPropertyName, comparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { + var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); throw new IllegalArgumentException(""" The valueSelectorConfig (%s) with resolvedSelectionOrder (%s) needs \ - a sorterManner (%s) or a sorterComparatorClass (%s) or a sorterWeightFactoryClass (%s) \ + a sorterManner (%s) or a sorterComparatorClass (%s) or a %s (%s) \ or a sorterClass (%s).""" .formatted(config, resolvedSelectionOrder, sorterManner, config.getSorterComparatorClass(), - config.getSorterWeightFactoryClass(), config.getSorterClass())); + comparatorFactoryPropertyName, comparatorFactoryClass, config.getSorterClass())); } if (!valueSelector.getVariableDescriptor().canExtractValueRangeFromSolution() && resolvedCacheType == SelectionCacheType.STEP) { diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index cd23b612ca..e6dbe1d936 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -531,6 +531,8 @@ + + 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 5d8e122398..d13b6e9e07 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 @@ -10,8 +10,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; @@ -29,12 +29,11 @@ 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.heuristic.selector.value.ValueSorterManner; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; -import ai.timefold.solver.core.testdomain.TestdataObject; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.common.DummyHardSoftEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableFactory; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; @@ -1293,6 +1292,66 @@ void solveEntityFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { } } + private static List generateValueFactorySortingConfiguration() { + var values = new ArrayList(); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig().withId("sortedEntitySelector")) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig(new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterWeightFactoryClass(TestdataObjectSortableFactory.class)))), + new int[] { 2, 1, 0 }, + // Only values are sorted + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig().withId("sortedEntitySelector")) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig(new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterComparatorFactoryClass(TestdataObjectSortableFactory.class)))), + new int[] { 2, 1, 0 }, + // Only values are sorted + false)); + return values; + } + + @ParameterizedTest + @MethodSource("generateValueFactorySortingConfiguration") + void solveValueFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { + var solverConfig = + PlannerTestUtils + .buildSolverConfig(TestdataSolution.class, TestdataEntity.class) + .withEasyScoreCalculatorClass(TestdataSolutionEasyScoreCalculator.class) + .withPhases(phaseConfig.config()); + + var solution = TestdataSolution.generateUninitializedSolution(3, 3); + + solution = PlannerTestUtils.solve(solverConfig, solution); + assertThat(solution).isNotNull(); + if (phaseConfig.expected() != null) { + for (var i = 0; i < 3; i++) { + var id = "Generated Entity %d".formatted(i); + var entity = solution.getEntityList().stream() + .filter(e -> e.getCode().equals(id)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + assertThat(entity.getValue()).isNotNull(); + assertThat(TestdataObjectSortableFactory.extractCode(entity.getValue().getCode())) + .isEqualTo(phaseConfig.expected[i]); + } + } + } + @Test void failConstructionHeuristicEntityRange() { var solverConfig = @@ -1500,23 +1559,25 @@ public static class TestdataListSolutionEasyScoreCalculator } } - public static class TestdataObjectSortableFactory - implements SelectionSorterWeightFactory, - ComparatorFactory { + public static class TestdataSolutionEasyScoreCalculator + implements EasyScoreCalculator { @Override - public Comparable createSorterWeight(TestdataListSolution solution, TestdataObject selection) { - return createSorter(solution, selection); - } - - @Override - public Comparable createSorter(TestdataListSolution solution, TestdataObject selection) { - return -extractCode(selection.getCode()); - } - - public static int extractCode(String code) { - var idx = code.lastIndexOf(" "); - return Integer.parseInt(code.substring(idx + 1)); + public @NonNull SimpleScore + calculateScore(@NonNull TestdataSolution solution) { + var score = 0; + var distinct = (int) solution.getEntityList().stream() + .map(TestdataEntity::getValue) + .filter(Objects::nonNull) + .distinct() + .count(); + var assigned = solution.getEntityList().stream() + .map(TestdataEntity::getValue) + .filter(Objects::nonNull) + .count(); + var repeated = (int) (assigned - distinct); + score -= repeated; + return SimpleScore.of(score); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index cc8e27cd0b..18358cee74 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -197,7 +197,7 @@ void failFast_ifMimicRecordingIsUsedWithOtherProperty() { @Test void failFast_ifBothFactoriesUsed() { - EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() + var entitySelectorConfig = new EntitySelectorConfig() .withSorterManner(EntitySorterManner.DESCENDING) .withCacheType(SelectionCacheType.PHASE) .withSelectionOrder(SelectionOrder.SORTED) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index d214f6fcd8..7b71ae5f17 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -16,6 +16,7 @@ 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.value.ValueSelectorConfig; +import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner; 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; @@ -260,6 +261,25 @@ void failFast_ifMimicRecordingIsUsedWithOtherProperty() { .withMessageContaining("has another property"); } + @Test + void failFast_ifBothFactoriesUsed() { + var valueSelectorConfig = new ValueSelectorConfig() + .withSorterManner(ValueSorterManner.DESCENDING) + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class) + .withSorterComparatorFactoryClass(DummySelectionComparatorFactory.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> ValueSelectorFactory. create(valueSelectorConfig) + .buildValueSelector(buildHeuristicConfigPolicy(), TestdataEntity.buildEntityDescriptor(), + SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining( + "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory)") + .withMessageContaining( + "and sorterComparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); + } + static Stream applyListValueFiltering() { return Stream.of( arguments(true, ValueSelectorFactory.ListValueFilteringType.ACCEPT_ASSIGNED, AssignedListValueSelector.class), diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java new file mode 100644 index 0000000000..cf85000aad --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java @@ -0,0 +1,24 @@ +package ai.timefold.solver.core.testdomain.common; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataObjectSortableFactory implements SelectionSorterWeightFactory, + ComparatorFactory { + + @Override + public Comparable createSorterWeight(Object solution, TestdataObject selection) { + return createSorter(solution, selection); + } + + @Override + public Comparable createSorter(Object solution, TestdataObject selection) { + return -extractCode(selection.getCode()); + } + + public static int extractCode(String code) { + var idx = code.lastIndexOf(" "); + return Integer.parseInt(code.substring(idx + 1)); + } +} From 3bd71b9fa5697a9dd674eea9d52f96fa4f44f664 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 20 Oct 2025 13:37:14 -0300 Subject: [PATCH 19/33] chore: add new factory class setting to move config --- benchmark/src/main/resources/benchmark.xsd | 3 + core/src/build/revapi-differences.json | 11 ++ .../selector/move/MoveSelectorConfig.java | 37 +++++++ .../entity/EntitySelectorFactory.java | 8 +- .../move/AbstractMoveSelectorFactory.java | 79 +++++++++----- .../selector/value/ValueSelectorFactory.java | 8 +- core/src/main/resources/solver.xsd | 2 + .../entity/EntitySelectorFactoryTest.java | 1 + .../move/MoveSelectorFactoryTest.java | 47 ++++++++ .../decorator/SortingMoveSelectorTest.java | 101 ++++++++++++++++++ .../value/ValueSelectorFactoryTest.java | 1 + 11 files changed, 265 insertions(+), 33 deletions(-) diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index 93b89abc92..084a742e2a 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -1238,6 +1238,9 @@ + + + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index 1c8e7fb9cd..df27fb07fa 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -415,6 +415,17 @@ "oldValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "newValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "justification": "New comparator factory field" + }, + { + "ignore": true, + "code": "java.annotation.attributeValueChanged", + "old": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "annotationType": "jakarta.xml.bind.annotation.XmlType", + "attribute": "propOrder", + "oldValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", + "newValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", + "justification": "New comparator factory field" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 39a2873297..729c1478b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -68,6 +68,7 @@ "filterClass", "sorterComparatorClass", "sorterWeightFactoryClass", + "sorterComparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -82,7 +83,12 @@ public abstract class MoveSelectorConfig filterClass = null; protected Class sorterComparatorClass = null; + /** + * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; + protected Class sorterComparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -128,14 +134,31 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } + /** + * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @param sorterWeightFactoryClass the class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } + public Class getSorterComparatorFactoryClass() { + return sorterComparatorFactoryClass; + } + + public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { + this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + } + public @Nullable SelectionSorterOrder getSorterOrder() { return sorterOrder; } @@ -201,12 +224,23 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { return (Config_) this; } + /** + * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @param sorterWeightFactoryClass the factory class + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull Config_ withSorterWeightFactoryClass( @NonNull Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } + public @NonNull Config_ + withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setSorterComparatorFactoryClass(comparatorFactoryClass); + return (Config_) this; + } + public @NonNull Config_ withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { this.sorterOrder = sorterOrder; return (Config_) this; @@ -259,6 +293,7 @@ protected void visitCommonReferencedClasses(@NonNull Consumer> classVis classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); classVisitor.accept(sorterWeightFactoryClass); + classVisitor.accept(sorterComparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } @@ -271,6 +306,8 @@ private void inheritCommon(MoveSelectorConfig inheritedConfig) { sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); + sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 5c277c782d..63c4f6373d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -196,7 +196,7 @@ private static String determineSorterComparatorFactoryPropertyName(EntitySelecto return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; } - private Class + private static Class determineSorterComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { var propertyName = determineSorterComparatorFactoryPropertyName(entitySelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { @@ -291,11 +291,13 @@ has a resolvedSelectionOrder (%s) that is not %s.""" resolvedSelectionOrder, SelectionOrder.SORTED)); } assertNotSorterMannerAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); + assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, + EntitySelectorFactory::determineSorterComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", EntitySelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); assertNotSorterClassAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); + assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, + EntitySelectorFactory::determineSorterComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { throw new IllegalArgumentException( diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 799c603c8d..7b98ef8fdc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -120,6 +120,28 @@ protected boolean determineBaseRandomSelection(SelectionCacheType resolvedCacheT }; } + private String determineSorterComparatorFactoryPropertyName(MoveSelectorConfig_ moveSelectorConfig) { + var weightFactoryClass = moveSelectorConfig.getSorterWeightFactoryClass(); + var comparatorFactoryClass = moveSelectorConfig.getSorterComparatorFactoryClass(); + if (weightFactoryClass != null && comparatorFactoryClass != null) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) cannot have a %s (%s) and %s (%s) at the same time.".formatted( + moveSelectorConfig, "sorterWeightFactoryClass", weightFactoryClass, + "sorterComparatorFactoryClass", comparatorFactoryClass)); + } + return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; + } + + private Class + determineSorterComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { + var propertyName = determineSorterComparatorFactoryPropertyName(moveSelectorConfig); + if (propertyName.equals("sorterWeightFactoryClass")) { + return moveSelectorConfig.getSorterWeightFactoryClass(); + } else { + return moveSelectorConfig.getSorterComparatorFactoryClass(); + } + } + protected boolean isBaseInherentlyCached() { return false; } @@ -149,36 +171,37 @@ private MoveSelector applyFiltering(MoveSelector moveSelec } protected void validateSorting(SelectionOrder resolvedSelectionOrder) { - if ((config.getSorterComparatorClass() != null || config.getSorterWeightFactoryClass() != null + var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); + var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); + if ((config.getSorterComparatorClass() != null || sorterComparatorFactoryClass != null || config.getSorterOrder() != null || config.getSorterClass() != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and sorterOrder (" + config.getSorterOrder() - + ") and sorterClass (" + config.getSorterClass() - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") that is not " + SelectionOrder.SORTED + "."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with sorterComparatorClass (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) has a resolvedSelectionOrder (%s) that is not %s." + .formatted(config, config.getSorterComparatorClass(), sorterComparatorFactoryPropertyName, + sorterComparatorFactoryClass, config.getSorterOrder(), config.getSorterClass(), + resolvedSelectionOrder, SelectionOrder.SORTED)); } - if (config.getSorterComparatorClass() != null && config.getSorterWeightFactoryClass() != null) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ")."); + if (config.getSorterComparatorClass() != null && sorterComparatorFactoryClass != null) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s).".formatted(config, + config.getSorterComparatorClass(), sorterComparatorFactoryPropertyName, + sorterComparatorFactoryClass)); } if (config.getSorterComparatorClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterClass (%s)." + .formatted(config, config.getSorterComparatorClass(), config.getSorterClass())); } - if (config.getSorterWeightFactoryClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") has both a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + if (sorterComparatorFactoryClass != null && config.getSorterClass() != null) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) has both a %s (%s) and a sorterClass (%s).".formatted(config, + sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass, config.getSorterClass())); } if (config.getSorterClass() != null && config.getSorterOrder() != null) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with sorterClass (" + config.getSorterClass() - + ") has a non-null sorterOrder (" + config.getSorterOrder() + ")."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with sorterClass (%s) has a non-null sorterOrder (%s).".formatted(config, + config.getSorterClass(), config.getSorterOrder())); } } @@ -187,24 +210,26 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter> sorter; var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterWeightFactoryClass = config.getSorterWeightFactoryClass(); + var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); + var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); var sorterClass = config.getSorterClass(); if (sorterComparatorClass != null) { Comparator> sorterComparator = ConfigUtils.newInstance(config, "sorterComparatorClass", sorterComparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (sorterWeightFactoryClass != null) { + } else if (sorterComparatorFactoryClass != null) { ComparatorFactory> comparatorFactory = - ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); + ConfigUtils.newInstance(config, sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); } else { throw new IllegalArgumentException( - "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a sorterComparatorClass (%s) or a sorterWeightFactoryClass (%s) or a sorterClass (%s)." - .formatted(config, resolvedSelectionOrder, sorterComparatorClass, sorterWeightFactoryClass, + "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a sorterComparatorClass (%s) or a %s (%s) or a sorterClass (%s)." + .formatted(config, resolvedSelectionOrder, sorterComparatorClass, + sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass, sorterClass)); } moveSelector = new SortingMoveSelector<>(moveSelector, resolvedCacheType, sorter); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 066898b86c..a3a226a607 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -228,7 +228,7 @@ private static String determineSorterComparatorFactoryPropertyName(ValueSelector return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; } - private Class + private static Class determineSorterComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { var propertyName = determineSorterComparatorFactoryPropertyName(valueSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { @@ -309,11 +309,13 @@ has a resolvedSelectionOrder (%s) that is not %s.""" SelectionOrder.SORTED)); } assertNotSorterMannerAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); + assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, + ValueSelectorFactory::determineSorterComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", ValueSelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); assertNotSorterClassAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, this::determineSorterComparatorFactoryClass); + assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, + ValueSelectorFactory::determineSorterComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { throw new IllegalArgumentException( diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index e6dbe1d936..af7582dec4 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -631,6 +631,8 @@ + + diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 18358cee74..616195349d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -207,6 +207,7 @@ void failFast_ifBothFactoriesUsed() { assertThatIllegalArgumentException() .isThrownBy(() -> EntitySelectorFactory. create(entitySelectorConfig) .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining("The entitySelectorConfig") .withMessageContaining( "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory)") .withMessageContaining( diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index 61148d8ef7..e6c63ee7b4 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -22,6 +22,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ShufflingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.SortingMoveSelector; import ai.timefold.solver.core.testdomain.TestdataSolution; +import ai.timefold.solver.core.testdomain.common.DummyValueFactory; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; @@ -212,6 +213,34 @@ void applySorting_withSorterComparatorClass() { assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); } + @Test + void applySorting_withSorterComparatorFactoryClass() { + // Old setting + { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterWeightFactoryClass(DummyValueFactory.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } + // New setting + { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterComparatorFactoryClass(DummyValueFactory.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } + } + @Test void applyProbability_withProbabilityWeightFactoryClass() { final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); @@ -250,6 +279,24 @@ public Move doMove(ScoreDirector scoreDirect assertThat(moveSelector.iterator().hasNext()).isFalse(); } + @Test + void failFast_ifBothFactoriesUsed() { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterWeightFactoryClass(DummyValueFactory.class); + moveSelectorConfig.setSorterComparatorFactoryClass(DummyValueFactory.class); + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + assertThatIllegalArgumentException() + .isThrownBy(() -> moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, + baseMoveSelector)) + .withMessageContaining("The moveSelectorConfig") + .withMessageContaining( + "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.testdomain.common.DummyValueFactory)") + .withMessageContaining( + "and sorterComparatorFactoryClass (class ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time"); + } + static class DummyMoveSelectorConfig extends MoveSelectorConfig { @Override diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index da7fe8df12..36b38b6705 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -1,7 +1,9 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.decorator; +import static ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicyTestUtils.buildHeuristicConfigPolicy; import static ai.timefold.solver.core.testutil.PlannerAssert.assertAllCodesOfMoveSelector; import static ai.timefold.solver.core.testutil.PlannerAssert.verifyPhaseLifecycle; +import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockScoreDirector; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -9,18 +11,31 @@ import static org.mockito.Mockito.when; import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; 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.common.decorator.SelectionSorterOrder; +import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; +import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.move.DummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; +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.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.testdomain.TestdataSolution; +import ai.timefold.solver.core.testutil.CodeAssertable; +import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; class SortingMoveSelectorTest { @@ -44,6 +59,41 @@ void cacheTypeJustInTime() { assertThatIllegalArgumentException().isThrownBy(() -> runCacheType(SelectionCacheType.JUST_IN_TIME, 5)); } + private static List generateConfiguration() { + return List.of( + new DummySorterMoveSelectorConfig() + .withSorterOrder(SelectionSorterOrder.ASCENDING) + .withSorterWeightFactoryClass(TestCodeAssertableComparatorFactory.class), + new DummySorterMoveSelectorConfig() + .withSorterOrder(SelectionSorterOrder.ASCENDING) + .withSorterComparatorFactoryClass(TestCodeAssertableComparatorFactory.class)); + } + + @ParameterizedTest + @MethodSource("generateConfiguration") + void applySorting(DummySorterMoveSelectorConfig moveSelectorConfig) { + var baseMoveSelector = SelectorTestUtils.mockMoveSelector( + new DummyMove("jan"), new DummyMove("feb"), new DummyMove("mar"), + new DummyMove("apr"), new DummyMove("may"), new DummyMove("jun")); + var moveSelectorFactory = new DummySorterMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + var moveSelector = + moveSelectorFactory.buildBaseMoveSelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, false); + + var scoreDirector = mockScoreDirector(TestdataSolution.buildSolutionDescriptor()); + var solverScope = mock(SolverScope.class); + when(solverScope.getScoreDirector()).thenReturn(scoreDirector); + moveSelector.solvingStarted(solverScope); + + var phaseScope = mock(AbstractPhaseScope.class); + when(phaseScope.getSolverScope()).thenReturn(solverScope); + moveSelector.phaseStarted(phaseScope); + + var stepScopeA = mock(AbstractStepScope.class); + when(stepScopeA.getPhaseScope()).thenReturn(phaseScope); + moveSelector.stepStarted(stepScopeA); + assertAllCodesOfMoveSelector(moveSelector, "apr", "feb", "jan", "jun", "mar", "may"); + } + public void runCacheType(SelectionCacheType cacheType, int timesCalled) { MoveSelector childMoveSelector = SelectorTestUtils.mockMoveSelector( new DummyMove("jan"), new DummyMove("feb"), new DummyMove("mar"), @@ -105,4 +155,55 @@ public void runCacheType(SelectionCacheType cacheType, int timesCalled) { verify(childMoveSelector, times(timesCalled)).getSize(); } + private static class DummySorterMoveSelectorConfig extends MoveSelectorConfig { + + @Override + public @NonNull DummySorterMoveSelectorConfig copyConfig() { + throw new UnsupportedOperationException(); + } + + @Override + public void visitReferencedClasses(@NonNull Consumer> classVisitor) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNearbySelectionConfig() { + return false; + } + } + + private static class DummySorterMoveSelectorFactory + extends AbstractMoveSelectorFactory { + + protected final MoveSelector baseMoveSelector; + + DummySorterMoveSelectorFactory(DummySorterMoveSelectorConfig moveSelectorConfig, + MoveSelector baseMoveSelector) { + super(moveSelectorConfig); + this.baseMoveSelector = baseMoveSelector; + } + + @Override + protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, + SelectionCacheType minimumCacheType, + boolean randomSelection) { + return applySorting(minimumCacheType, SelectionOrder.SORTED, baseMoveSelector); + } + } + + public static class TestCodeAssertableComparatorFactory + implements SelectionSorterWeightFactory, ComparatorFactory { + + @Override + public Comparable createSorterWeight(Object o, CodeAssertable selection) { + return selection.getCode(); + } + + @Override + public Comparable createSorter(Object o, CodeAssertable selection) { + return selection.getCode(); + } + } + } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 7b71ae5f17..891f591199 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -274,6 +274,7 @@ void failFast_ifBothFactoriesUsed() { .isThrownBy(() -> ValueSelectorFactory. create(valueSelectorConfig) .buildValueSelector(buildHeuristicConfigPolicy(), TestdataEntity.buildEntityDescriptor(), SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining("The valueSelectorConfig") .withMessageContaining( "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory)") .withMessageContaining( From 97a23a2e6b4f5ac77037547a1451469107bc1e82 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 20 Oct 2025 15:51:28 -0300 Subject: [PATCH 20/33] docs: update sorting documentation --- .../construction-heuristics.adoc | 103 +- .../exhaustive-search.adoc | 24 +- .../optimization-algorithms/overview.adoc | 17 +- .../modeling-planning-problems.adoc | 1029 +++++++++-------- 4 files changed, 609 insertions(+), 564 deletions(-) diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc index cea76665d0..4d139cbf33 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc @@ -69,12 +69,15 @@ For a very advanced configuration, see <>, but assigns the more difficult planning entities first, because they are less likely to fit in the leftovers. -So it sorts the planning entities on decreasing difficulty. +Like <>, but analyzes the "more difficult" planning entities first, +because they are less likely to fit in the leftovers. +It sorts the planning entities in descending order by any given metric, +meaning that "more difficult" planning entities are added earlier in the list while "less difficult" values are included later. image::optimization-algorithms/construction-heuristics/firstFitDecreasingNQueens04.png[align="center"] -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison]. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]. [NOTE] ==== @@ -118,10 +121,13 @@ For a very advanced configuration, see <>, but it analyzes the "weaker" planning values first, +because the "stronger" planning values are more likely to be able to accommodate later planning entities. +It sorts the planning values in ascending order by any given metric, +meaning that "weaker" planning values are added earlier in the list while "stronger" values are included later. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. [NOTE] ==== @@ -165,11 +171,12 @@ For a very advanced configuration, see <> and <>. +It sorts the planning entities in descending order and the planning values in ascending order based on specified metrics. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison] -and xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting] +and xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. [NOTE] ==== @@ -214,10 +221,13 @@ For a very advanced configuration, see <>, but it analyzes the "stronger" planning values first, +because the "stronger" planning values are more likely to have a lower soft cost to use. +So it sorts the planning values in descending order by any given metric, +meaning that "stronger" planning values are added earlier in the list. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. [NOTE] ==== @@ -261,11 +271,12 @@ For a very advanced configuration, see <> and <>. +It sorts the planning entities and the planning values in descending order based on specified metrics. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison] -and xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting] +and xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. [NOTE] ==== @@ -310,8 +321,10 @@ For a very advanced configuration, see <>, <>, -<>, <>, +Allocate Entity From Queue is a versatile generic form of <>, +<>, +<>, +<>, <> and <>. It works like this: @@ -338,27 +351,29 @@ Verbose simple configuration: ---- ALLOCATE_ENTITY_FROM_QUEUE - DECREASING_DIFFICULTY_IF_AVAILABLE - INCREASING_STRENGTH_IF_AVAILABLE + DESCENDING_IF_AVAILABLE + ASCENDING_IF_AVAILABLE ---- The `entitySorterManner` options are: -* ``DECREASING_DIFFICULTY``: Initialize the more difficult planning entities first. +* ``DESCENDING``: Evaluate the planning entities in descending order based on a given metric. This usually increases pruning (and therefore improves scalability). -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison]. -* `DECREASING_DIFFICULTY_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison], behave like ``DECREASING_DIFFICULTY``, else like ``NONE``. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]. +* `DESCENDING_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting], behave like ``DESCENDING``, else like ``NONE``. * ``NONE``: Initialize the planning entities in original order. The `valueSorterManner` options are: -* ``INCREASING_STRENGTH``: Evaluate the planning values in increasing strength. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. -* `INCREASING_STRENGTH_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison], behave like ``INCREASING_STRENGTH``, else like ``NONE``. -* ``DECREASING_STRENGTH``: Evaluate the planning values in decreasing strength. -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. -* ``DECREASING_STRENGTH_IF_AVAILABLE``: If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison], behave like ``DECREASING_STRENGTH``, else like ``NONE``. +* ``ASCENDING``: Evaluate the planning values in ascending order based on a given metric. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. +* `ASCENDING_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting], behave like ``ASCENDING``, else like ``NONE``. +* ``DESCENDING``: Evaluate the planning values in descending order based on a given metric. +Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. +* ``DESCENDING_IF_AVAILABLE``: If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting], behave like ``DESCENDING``, else like ``NONE``. * ``NONE``: Try the planning values in original order. Advanced configuration with <> for a single entity class with one variable: @@ -370,14 +385,14 @@ Advanced configuration with <> for PHASE SORTED - DECREASING_DIFFICULTY + DESCENDING PHASE SORTED - INCREASING_STRENGTH + ASCENDING @@ -529,8 +544,8 @@ Verbose simple configuration: ---- ALLOCATE_TO_VALUE_FROM_QUEUE - DECREASING_DIFFICULTY_IF_AVAILABLE - INCREASING_STRENGTH_IF_AVAILABLE + DESCENDING_IF_AVAILABLE + ASCENDING_IF_AVAILABLE ---- @@ -543,7 +558,7 @@ Advanced configuration for a single entity class with a single variable: PHASE SORTED - INCREASING_STRENGTH + ASCENDING @@ -552,7 +567,7 @@ Advanced configuration for a single entity class with a single variable: PHASE SORTED - DECREASING_DIFFICULTY + DESCENDING @@ -640,7 +655,7 @@ This algorithm has not been implemented yet. [#allocateFromPoolAlgorithm] === Algorithm description -Allocate From Pool is a versatile, generic form of <> and <>. +Allocate From Pool is a versatile generic form of <> and <>. It works like this: . Put all entity-value combinations in a pool. @@ -666,8 +681,8 @@ Verbose simple configuration: ---- ALLOCATE_FROM_POOL - DECREASING_DIFFICULTY_IF_AVAILABLE - INCREASING_STRENGTH_IF_AVAILABLE + DESCENDING_IF_AVAILABLE + ASCENDING_IF_AVAILABLE ---- @@ -683,12 +698,12 @@ Advanced configuration with <> for a singl PHASE SORTED - DECREASING_DIFFICULTY + DESCENDING PHASE SORTED - INCREASING_STRENGTH + ASCENDING @@ -755,14 +770,14 @@ Advanced configuration for a single entity class with a list variable and a sing PHASE SORTED - DECREASING_DIFFICULTY + DESCENDING PHASE SORTED - INCREASING_STRENGTH + ASCENDING @@ -774,7 +789,7 @@ Advanced configuration for a single entity class with a list variable and a sing PHASE SORTED - INCREASING_STRENGTH + ASCENDING diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/exhaustive-search.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/exhaustive-search.adoc index fd4e96ea8d..3885e7df00 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/exhaustive-search.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/exhaustive-search.adoc @@ -114,8 +114,8 @@ Advanced configuration: BRANCH_AND_BOUND DEPTH_FIRST - DECREASING_DIFFICULTY_IF_AVAILABLE - INCREASING_STRENGTH_IF_AVAILABLE + DESCENDING_IF_AVAILABLE + ASCENDING_IF_AVAILABLE ---- @@ -160,17 +160,21 @@ The `nodeExplorationType` options are: The `entitySorterManner` options are: -* ``DECREASING_DIFFICULTY``: Initialize the more difficult planning entities first. This usually increases pruning (and therefore improves scalability). -Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison]. -* `DECREASING_DIFFICULTY_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty comparison], behave like ``DECREASING_DIFFICULTY``, else like ``NONE``. +* ``DESCENDING``: Evaluate the planning entities in descending order based on a given metric. +This usually increases pruning (and therefore improves scalability). +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]. +* `DESCENDING_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting], behave like ``DESCENDING``, else like ``NONE``. * ``NONE``: Initialize the planning entities in original order. The `valueSorterManner` options are: -* ``INCREASING_STRENGTH``: Evaluate the planning values in increasing strength. Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. -* `INCREASING_STRENGTH_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison], behave like ``INCREASING_STRENGTH``, else like ``NONE``. -* ``DECREASING_STRENGTH``: Evaluate the planning values in decreasing strength. Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison]. -* ``DECREASING_STRENGTH_IF_AVAILABLE``: If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength comparison], behave like ``DECREASING_STRENGTH``, else like ``NONE``. +* ``ASCENDING``: Evaluate the planning values in ascending order based on a given metric. +Requires the model +to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. +* `ASCENDING_IF_AVAILABLE` (default): If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting], behave like ``ASCENDING``, else like ``NONE``. +* ``DESCENDING``: Evaluate the planning values in descending order based on a given metric. Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]. +* ``DESCENDING_IF_AVAILABLE``: If the model supports xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting], behave like ``DESCENDING``, else like ``NONE``. * ``NONE``: Try the planning values in original order. @@ -196,4 +200,4 @@ Use Construction Heuristics with Local Search instead: those can handle thousand ==== Throwing hardware at these scalability issues has no noticeable impact. Moore's law cannot win against the onslaught of a few more planning entities in the dataset. -==== \ No newline at end of file +==== diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index a0b2ef41e9..a4176282ef 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -125,7 +125,8 @@ However, this basic procedure provides a good starting configuration that will p . Start with a quick configuration that involves little or no configuration and optimization code: See xref:optimization-algorithms/construction-heuristics.adoc#firstFit[First Fit]. -. Next, implement xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty] comparison +. Next, +implement xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting] and turn it into xref:optimization-algorithms/construction-heuristics.adoc#firstFitDecreasing[First Fit Decreasing]. . Next, add Late Acceptance behind it: @@ -1538,25 +1539,25 @@ If you do explicitly configure the ``Selector``, it overwrites the default setti Some `Selector` types implement a `SorterManner` out of the box: * `EntitySelector` supports: -** ``DECREASING_DIFFICULTY``: Sorts the planning entities according to decreasing xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntityDifficulty[planning entity difficulty]. Requires that planning entity difficulty is annotated on the domain model. +** ``DESCENDING``: Sorts the planning entities in descending order based on a give metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting]). Requires that planning entity is annotated on the domain model. + [source,xml,options="nowrap"] ---- PHASE SORTED - DECREASING_DIFFICULTY + DESCENDING ---- * `ValueSelector` supports: -** ``INCREASING_STRENGTH``: Sorts the planning values according to increasing xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueStrength[planning value strength]. Requires that planning value strength is annotated on the domain model. +** ``ASCENDING``: Sorts the planning values in ascending order based on a given metric (xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueSorting[planning value sorting]). Requires that planning value is annotated on the domain model. + [source,xml,options="nowrap"] ---- PHASE SORTED - INCREASING_STRENGTH + ASCENDING ---- @@ -1568,7 +1569,7 @@ An easy way to sort a `Selector` is with a plain old ``Comparator``: [source,java,options="nowrap"] ---- -public class VisitDifficultyComparator implements Comparator { +public class VisitComparator implements Comparator { public int compare(Visit a, Visit b) { return new CompareToBuilder() @@ -1587,7 +1588,7 @@ You'll also need to configure it (unless it's annotated on the domain model and PHASE SORTED - ...VisitDifficultyComparator + ...VisitComparator DESCENDING ---- @@ -1620,7 +1621,7 @@ You'll also need to configure it (unless it's annotated on the domain model and PHASE SORTED - ...MyDifficultyWeightFactory + ...MySorterComparatorFactory DESCENDING ---- diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc index f35a380a66..eb0cae156c 100644 --- a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc +++ b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc @@ -372,27 +372,28 @@ As Timefold Solver will mutate the <> or < { +public class VisitComparator implements Comparator { public int compare(Visit a, Visit b) { return new CompareToBuilder() @@ -412,20 +413,20 @@ public class VisitDifficultyComparator implements Comparator { } ---- -Alternatively, you can also set a `difficultyWeightFactoryClass` to the `@PlanningEntity` annotation, +Alternatively, you can also set a `comparatorFactoryClass` to the `@PlanningEntity` annotation, so that you have access to the rest of the problem facts from the solution too. See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] for more information. [IMPORTANT] ==== -Difficulty should be implemented ascending: easy entities are lower, difficult entities are higher. +Entities should be sorted in ascending order: easy entities are lower, difficult entities are higher. For example, in bin packing: small item < medium item < big item. -Although most algorithms start with the more difficult entities first, they just reverse the ordering. +Although most algorithms start in descending order, they just reverse the ordering. ==== -_None of the current planning variable states should be used to compare planning entity difficulty._ +_None of the current planning variable states should be used to compare planning entities._ During Construction Heuristics, those variables are likely to be `null` anyway. For example, a ``Lesson``'s `timeslot` variable should not be used. @@ -663,62 +664,70 @@ Furthermore, it must be a mutable `Collection` because once Timefold Solver star it will add and remove elements to the ``Collection``s of those shadow variables accordingly. ==== -[#planningValueAndPlanningValueRange] -== Planning value and planning value range - -[#planningValue] -=== Planning value +[#planningListVariable] +== Planning list variable (VRP, Task assigning, ...) -A planning value is a possible value for a genuine planning variable. -Usually, a planning value is a problem fact, but it can also be any object, for example an ``Integer``. -It can even be another planning entity or even an interface implemented by both a planning entity and a problem fact. +Use the planning list variable to model problems where the goal is to distribute a number of workload elements among limited resources in a specific order. +This includes, for example, vehicle routing, traveling salesman, task assigning, and similar problems. [NOTE] ==== -Primitive types (such as ``int``) are not allowed. +Use a <> instead of a planning list variable, +if you need any of the following planning techniques: + +- xref:optimization-algorithms/exhaustive-search.adoc#exhaustiveSearch[exhaustive search], +- xref:enterprise-edition/enterprise-edition.adoc#partitionedSearch[partitioned search], +- coexistence with another list variable. ==== -A planning value range is the set of possible planning values for a planning variable. -Planning value ranges need to come from a finite collection. +For example, the vehicle routing problem can be modeled as follows: +image::quickstart/vehicle-routing/vehicleRoutingClassDiagramAnnotated.png[] -[#planningValueRangeProvider] -=== Planning value range provider +This model is closer to the reality than the chained model. +Each vehicle has a list of customers to go to in the order given by the list. +And indeed, the object model matches the natural language description of the problem: +[tabs] +==== +Java:: ++ +[source,java,options="nowrap"] +---- +@PlanningEntity +class Vehicle { -[#planningValueRangeProviderOverview] -==== Overview + int capacity; + Depot depot; -The value range of a planning variable is defined with the `@ValueRangeProvider` annotation. -A `@ValueRangeProvider` may carry a property ``id``, which is referenced by the ``@PlanningVariable``'s property ``valueRangeProviderRefs``. + @PlanningListVariable + List customers = new ArrayList<>(); +} +---- -This annotation can be located on two types of methods: -* On the Solution: All planning entities share the same value range. -* On the planning entity: The value range differs per planning entity. This is less common. +==== +Planning list variable can be used if the domain meets the following criteria: -[NOTE] -==== -A `@ValueRangeProvider` annotation needs to be on a member -in a class with a `@PlanningSolution` or a `@PlanningEntity` annotation. -It is ignored on parent classes or subclasses without those annotations. -==== +. There is a one-to-many relationship between the planning entity and the planning value. -The return type of that method can be three types: +. The order in which planning values are assigned to an entity's list variable is significant. -* ``Collection``: The value range is defined by a `Collection` (usually a ``List``) of its possible values. -* Array: The value range is defined by an array of its possible values. -* ``CountableValueRange``: The value range is defined by its bounds. This is less common. +. Each planning value is assigned to exactly one planning entity. +No planning value may appear in multiple entities. -[#valueRangeProviderOnSolution] -==== `ValueRangeProvider` on the solution -All instances of the same planning entity class share the same set of possible planning values for that planning variable. -This is the most common way to configure a value range. +[#planningListVariableAllowingUnassigned] +=== Allowing unassigned values -The `@PlanningSolution` implementation has a method that returns a `Collection` (or a ``CountableValueRange``). -Any value from that `Collection` is a possible planning value for this planning variable. +By default, all planning values have to be assigned to exactly one list variable across the entire planning model. +In an xref:responding-to-change/responding-to-change.adoc#overconstrainedPlanning[over-constrained use case], +this can be counterproductive. +For example: in task assignment with too many tasks for the workforce, +we would rather leave low priority tasks unassigned instead of assigning them to an overloaded worker. + +To allow a planning value to be unassigned, set `allowsUnassignedValues` to ``true``: [tabs] ==== @@ -726,60 +735,76 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningVariable -public Timeslot getTimeslot() { - return timeslot; -} ----- -+ -[source,java,options="nowrap"] ----- -@PlanningSolution -public class Timetable { - ... - - @ValueRangeProvider - public List getTimeslots() { - return timeslots; - } - +@PlanningListVariable(allowsUnassignedValues = true) +public List getCustomers() { + return customers; } ---- ==== [IMPORTANT] ==== -That `Collection` (or ``CountableValueRange``) must not contain the value ``null``, -not even for a <>. +Constraint Streams filter out unassigned planning values by default. +Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingUnassigned()] to avoid such unwanted behaviour. +Using a planning list variable with unassigned values implies +that your score calculation is responsible for punishing (or even rewarding) these unassigned values. + +Failure to penalize unassigned values can cause a solution with *all* values unassigned to be the best solution. +See the xref:responding-to-change/responding-to-change.adoc#overconstrainedPlanningWithNullValues[overconstrained planning with `null` variable values] section in the docs for more infomation. ==== -xref:using-timefold-solver/configuration.adoc#annotationAlternatives[Annotating the field] instead of the property works too: +xref:responding-to-change/responding-to-change.adoc[Repeated planning] +(especially xref:responding-to-change/responding-to-change.adoc#realTimePlanning[real-time planning]) +does not mix well with a planning list variable that allows unassigned values. +Every time the Solver starts or a problem fact change is made, +the xref:optimization-algorithms/construction-heuristics.adoc#constructionHeuristics[Construction Heuristics] +will try to initialize all the `null` variables again, which can be a huge waste of time. +One way to deal with this is to filter the entity selector of the placer in the construction heuristic. -[tabs] -==== -Java:: -+ -[source,java,options="nowrap"] +[source,xml,options="nowrap"] ---- -@PlanningSolution -public class Timetable { + + ... + + + + ... + + + ... + + + ... + + ... + +---- - @ValueRangeProvider - private List timeslots; -} ----- +[#listVariableShadowVariables] +=== List variable shadow variables +When the planning entity uses a <>, +you can use the following built-in annotations to derive shadow variables from that genuine planning variable. -==== +- xref:listVariableShadowVariablesInverseRelation[@InverseRelationShadowVariable]: Used to get the planning entity containing the planning variable list to which a planning value is assigned; +- xref:listVariableShadowVariablesIndex[@IndexShadowVariable]: Used to get the index of a planning value's position it's assigned planning variable list; +- xref:listVariableShadowVariablesPreviousAndNext[@PreviousElementShadowVariable]: Used to get a planning value's predecessor in its assigned planning variable list; +- xref:listVariableShadowVariablesPreviousAndNext[@NextElementShadowVariable]: Used to get a planning value's successor in its assigned planning variable list; +- xref:tailChainVariable[@CascadingUpdateShadowVariable]: Used to update a set of connected elements; +If the built-in shadow variable annotations are insufficient, xref:customShadowVariable[@ShadowVariable] can be used to create custom handlers. -[#valueRangeProviderOnPlanningEntity] -==== `ValueRangeProvider` on the Planning Entity +[#listVariableShadowVariablesInverseRelation] +==== Inverse relation shadow variable -Each planning entity has its own value range (a set of possible planning values) for the planning variable. -For example, if a teacher can *never* teach in a room that does not belong to their department, lectures of that teacher can limit their room value range to the rooms of their department. +Use the `@InverseRelationShadowVariable` annotation to establish bi-directional relationship between the entity and the elements assigned to its list variable. +The type of the inverse shadow variable is the planning entity itself +because there is a one-to-many relationship between the entity and the element classes. + +The planning entity side has a genuine list variable: [tabs] ==== @@ -787,65 +812,55 @@ Java:: + [source,java,options="nowrap"] ---- - @PlanningVariable - public Room getRoom() { - return room; - } +@PlanningEntity +public class Vehicle { - @ValueRangeProvider - public List getPossibleRoomList() { - return getCourse().getTeacher().getDepartment().getRoomList(); + @PlanningListVariable + public List getCustomers() { + return customers; } + + public void setCustomers(List customers) {...} +} ---- -==== -Never use this to enforce a soft constraint (or even a hard constraint when the problem might not have a feasible solution). For example: __Unless there is no other way__, a teacher cannot teach in a room that does not belong to their department. -In this case, the teacher should _not_ be limited in their room value range (because sometimes there is no other way). -[NOTE] -==== -By limiting the value range specifically of one planning entity, you are effectively creating a __built-in hard constraint__. -This can have the benefit of severely lowering the number of possible solutions; however, it can also take away the freedom of the optimization algorithms to temporarily break that constraint in order to escape from a local optimum. ==== -A planning entity should _not_ use other planning entities to determine its value range. -That would only try to make the planning entity solve the planning problem itself and interfere with the optimization algorithms. - -Every entity has its own `List` instance, unless multiple entities have the same value range. -For example, if teacher A and B belong to the same department, they use the same `List` instance. -Furthermore, each `List` contains a subset of the same set of planning value instances. -For example, if department A and B can both use room X, then their `List` instances contain the same `Room` instance. +On the element side: -[NOTE] -==== -A `ValueRangeProvider` on the planning entity consumes more memory than `ValueRangeProvider` on the Solution and disables certain automatic performance optimizations. -==== +- Annotate the class with `@PlanningEntity` to make it a shadow planning entity. +- <>, otherwise Timefold Solver won't detect it and the shadow variable won't update. +- Create a property with the genuine planning entity type. +- Annotate it with `@InverseRelationShadowVariable` and set `sourceVariableName` to the name of the genuine planning list variable. -[WARNING] -==== -A `ValueRangeProvider` on the planning entity is not currently compatible with a <> variable. +[tabs] ==== +Java:: ++ +[source,java,options="nowrap"] +---- +@PlanningEntity +public class Customer { -[#referencingValueRangeProviders] -==== Referencing ``ValueRangeProvider``s + @InverseRelationShadowVariable(sourceVariableName = "customers") + public Vehicle getVehicle() { + return vehicle; + } -There are two ways how to match a planning variable to a value range provider. -The simplest way is to have value range provider auto-detected. -Another way is to explicitly reference the value range provider. + public void setVehicle(Vehicle vehicle) {...} +} +---- +==== -[#anonymousValueRangeProviders] -===== Anonymous ``ValueRangeProvider``s +[#listVariableShadowVariablesIndex] +==== Index shadow variable -We already described the first approach. -By not providing any `valueRangeProviderRefs` on the `@PlanningVariable` annotation, -Timefold Solver will go over every ``@ValueRangeProvider``-annotated method or field which does not have an ``id`` property set, -and will match planning variables with value ranges where their types match. +While the `@InverseRelationShadowVariable` allows to establish the bi-directional relationship between the entity +and the elements assigned to its list variable, +`@IndexShadowVariable` provides a pointer into the entity's list variable where the element is assigned. -In the following example, -the planning variable ``car`` will be matched to the value range returned by ``getCompanyCarList()``, -as they both use the ``Car`` type. -It will not match ``getPersonalCarList()``, -because that value range provider is not anonymous; it specifies an ``id``. +The planning entity side has a genuine list variable: [tabs] ==== @@ -853,31 +868,29 @@ Java:: + [source,java,options="nowrap"] ---- - @PlanningVariable - public Car getCar() { - return car; +@PlanningEntity +public class Vehicle { + + @PlanningListVariable + public List getCustomers() { + return customers; } - @ValueRangeProvider - public List getCompanyCarList() { - return companyCarList; - } - - @ValueRangeProvider(id = "personalCarRange") - public List getPersonalCarList() { - return personalCarList; - } + public void setCustomers(List customers) {...} +} ---- ==== -Automatic matching also accounts for polymorphism. -In the following example, -the planning variable ``car`` will be matched to ``getCompanyCarList()`` and ``getPersonalCarList()``, -as both ``CompanyCar`` and ``PersonalCar`` are ``Car``s. -It will not match ``getAirplanes()``, -as an ``Airplane`` is not a ``Car``. +On the element side: + +- Annotate the class with `@PlanningEntity` to make it a shadow planning entity. +- <>, +otherwise Timefold Solver won't detect it and the shadow variable won't update. +- Create a property which returns an `Integer`. +`Integer` is required instead of `int`, as the index may be `null` if the element is not yet assigned to the list variable. +- Annotate it with `@IndexShadowVariable` and set `sourceVariableName` to the name of the genuine planning list variable. [tabs] ==== @@ -885,36 +898,27 @@ Java:: + [source,java,options="nowrap"] ---- - @PlanningVariable - public Car getCar() { - return car; - } - - @ValueRangeProvider - public List getCompanyCarList() { - return companyCarList; - } +@PlanningEntity +public class Customer { - @ValueRangeProvider - public List getPersonalCarList() { - return personalCarList; + @IndexShadowVariable(sourceVariableName = "customers") + public Integer getIndexInVehicle() { + return indexInVehicle; } - @ValueRangeProvider - public List getAirplanes() { - return airplaneList; - } +} ---- ==== -[#explicitlyReferencingValueRangeProviders] -===== Explicitly referenced ``ValueRangeProvider``s +[#listVariableShadowVariablesPreviousAndNext] +==== Previous and next element shadow variable -In more complicated cases where auto-detection is not sufficient or where clarity is preferred over simplicity, -value range providers can also be referenced explicitly. +Use `@PreviousElementShadowVariable` or `@NextElementShadowVariable` to get a reference to an element that is assigned to the same entity's list variable one index lower (previous element) or one index higher (next element). -In the following example, -the ``car`` planning variable will only be matched to value range provided by methods ``getCompanyCarList()``. +NOTE: The previous and next element shadow variables may be `null` even in a fully initialized solution. +The first element's previous shadow variable is `null` and the last element's next shadow variable is `null`. + +The planning entity side has a genuine list variable: [tabs] ==== @@ -922,26 +926,22 @@ Java:: + [source,java,options="nowrap"] ---- - @PlanningVariable(valueRangeProviderRefs = {"companyCarRange"}) - public Car getCar() { - return car; - } +@PlanningEntity +public class Vehicle { - @ValueRangeProvider(id = "companyCarRange") - public List getCompanyCarList() { - return companyCarList; + @PlanningListVariable + public List getCustomers() { + return customers; } - @ValueRangeProvider(id = "personalCarRange") - public List getPersonalCarList() { - return personalCarList; - } + public void setCustomers(List customers) {...} +} ---- ==== -Explicitly referenced value range providers can also be combined, for example: +On the element side: [tabs] ==== @@ -949,28 +949,36 @@ Java:: + [source,java,options="nowrap"] ---- - @PlanningVariable(valueRangeProviderRefs = { "companyCarRange", "personalCarRange" }) - public Car getCar() { - return car; - } +@PlanningEntity +public class Customer { - @ValueRangeProvider(id = "companyCarRange") - public List getCompanyCarList() { - return companyCarList; + @PreviousElementShadowVariable(sourceVariableName = "customers") + public Customer getPreviousCustomer() { + return previousCustomer; } - @ValueRangeProvider(id = "personalCarRange") - public List getPersonalCarList() { - return personalCarList; + public void setPreviousCustomer(Customer previousCustomer) {...} + + @NextElementShadowVariable(sourceVariableName = "customers") + public Customer getNextCustomer() { + return nextCustomer; } + + public void setNextCustomer(Customer nextCustomer) {...} ---- ==== +[#tailChainVariable] +=== Updating tail chains -[#valueRangeFactory] -==== `ValueRangeFactory` +The annotation `@CascadingUpdateShadowVariable` enables updates a set of connected elements. +Timefold Solver triggers a user-defined logic after all events are processed. +Hence, the related listener is the final one executed during the event lifecycle. +Moreover, +it automatically propagates changes to the subsequent elements in the list +when the value of the related shadow variable changes. -Instead of a ``Collection``, you can also return ``CountableValueRange``, built by the ``ValueRangeFactory``: +The planning entity side has a genuine list variable: [tabs] ==== @@ -978,18 +986,22 @@ Java:: + [source,java,options="nowrap"] ---- - @ValueRangeProvider - public CountableValueRange getDelayRange() { - return ValueRangeFactory.createIntValueRange(0, 5000); +@PlanningEntity +public class Vehicle { + + @PlanningListVariable + public List getCustomers() { + return customers; } + + public void setCustomers(List customers) {...} +} ---- ==== -A `CountableValueRange` uses far less memory, because it only holds the bounds. -In the example above, a `Collection` would need to hold all `5000` ints, instead of just the two bounds. -Furthermore, an `incrementUnit` can be specified, for example if you have to buy stocks in units of 200 pieces: +On the element side: [tabs] ==== @@ -997,153 +1009,133 @@ Java:: + [source,java,options="nowrap"] ---- - @ValueRangeProvider - public CountableValueRange getStockAmountRange() { - // Range: 0, 200, 400, 600, ..., 9999600, 9999800, 10000000 - return ValueRangeFactory.createIntValueRange(0, 10000000, 200); - } ----- -==== +@PlanningEntity +public class Customer { -The `ValueRangeFactory` has creation methods for several value class types: + @InverseRelationShadowVariable(sourceVariableName = "customers") + private Vehicle vehicle; + @PreviousElementShadowVariable(sourceVariableName = "customers") + private Customer previousCustomer; + @CascadingUpdateShadowVariable(targetMethodName = "updateArrivalTime") + private LocalDateTime arrivalTime; -* ``boolean``: A boolean range. -* ``int``: A 32bit integer range. -* ``long``: A 64bit integer range. -* ``BigInteger``: An arbitrary-precision integer range. -* ``BigDecimal``: A decimal point range. By default, the increment unit is the lowest non-zero value in the scale of the bounds. -* `Temporal` (such as ``LocalDate``, ``LocalDateTime``, ...): A time range. + ... + public void updateArrivalTime() {...} +---- +==== -[#planningValueStrength] -=== Planning value strength +The `targetMethodName` refers to the user-defined logic that updates the annotated shadow variable. +The method must be implemented in the defining entity class, be non-static, and not include any parameters. -Some optimization algorithms work a bit more efficiently if they have an estimation of which planning values are stronger, which means they are more likely to satisfy a planning entity. -For example: in bin packing bigger containers are more likely to fit an item. -Usually, the efficiency gain of planning value strength is far less than that of <>. +In the previous example, +the cascade update listener calls `updateArrivalTime` after all shadow variables have been updated, +including `vehicle` and `previousCustomer`. +It then automatically calls `updateArrivalTime` for the subsequent customers +and stops when the `arrivalTime` value does not change after running target method +or when it reaches the end. -[NOTE] +[WARNING] +==== +A user-defined logic can only change shadow variables. +Changing a genuine planning variable or a problem fact will result in score corruption. ==== -*Do not try to use planning value strength to implement a business constraint.* -It will not affect the score function: if we have infinite solving time, the returned solution will be the same. -To affect the score function, xref:constraints-and-score/overview.adoc#formalizeTheBusinessConstraints[add a score constraint]. -Only consider adding planning value strength too if it can make the solver more efficient. +[NOTE] +==== +When distinct target methods are used by separate `@CascadingUpdateShadowVariable` variables in the same model, +the order of their execution is undefined. ==== -To allow the heuristics to take advantage of that domain specific information, -set a `strengthComparatorClass` to the `@PlanningVariable` annotation: +==== Multiple sources -[source,java,options="nowrap"] ----- - @PlanningVariable(..., strengthComparatorClass = VehicleStrengthComparator.class) - public Vehicle getVehicle() { - return vehicle; - } ----- +If the user-defined logic requires updating multiple shadow variables, +apply the `@CascadingUpdateShadowVariable` to all shadow variables. +[tabs] +==== +Java:: ++ [source,java,options="nowrap"] ---- -public class VehicleStrengthComparator implements Comparator { +@PlanningEntity +public class Customer { - public int compare(Vehicle a, Vehicle b) { - return new CompareToBuilder() - .append(a.getCapacity(), b.getCapacity()) - .append(a.getId(), b.getId()) - .toComparison(); - } + @PreviousElementShadowVariable(sourceVariableName = "customers") + private Customer previousCustomer; + @NextElementShadowVariable(sourceVariableName = "customers") + private Customer nextCustomer; + @CascadingUpdateShadowVariable(targetMethodName = "updateWeightAndArrivalTime") + private LocalDateTime arrivalTime; + @CascadingUpdateShadowVariable(targetMethodName = "updateWeightAndArrivalTime") + private Integer weightAtVisit; + ... -} + public void updateWeightAndArrivalTime() {...} ---- -[NOTE] -==== -If you have multiple planning value classes in the _same_ value range, -the `strengthComparatorClass` needs to implement a `Comparator` of a common superclass (for example ``Comparator``) -and be able to handle comparing instances of those different classes. -==== - -Alternatively, you can also set a `strengthWeightFactoryClass` to the `@PlanningVariable` annotation, -so you have access to the rest of the problem facts from the solution too. - -See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] for more information. -[IMPORTANT] -==== -Strength should be implemented ascending: weaker values are lower, stronger values are higher. -In bin packing, small container < medium container < big container. ==== -_None of the current planning variable state in any of the planning entities should be used to compare planning values._ -During construction heuristics, those variables are likely to be ``null``. -For example, none of the `timeslot` variables of any `Lesson` may be used to determine the strength of a ``Timeslot``. +Timefold Solver triggers the user-defined logic in `updateWeightAndArrivalTime` at the end of the event lifecycle. +It stops when both `arrivalTime` and `weightAtVisit` values do not change or when it reaches the end. +[#planningValueAndPlanningValueRange] +== Planning value and planning value range -[#planningListVariable] -== Planning list variable (VRP, Task assigning, ...) +[#planningValue] +=== Planning value -Use the planning list variable to model problems where the goal is to distribute a number of workload elements among limited resources in a specific order. -This includes, for example, vehicle routing, traveling salesman, task assigning, and similar problems. +A planning value is a possible value for a genuine planning variable. +Usually, a planning value is a problem fact, but it can also be any object, for example an ``Integer``. +It can even be another planning entity or even an interface implemented by both a planning entity and a problem fact. [NOTE] ==== -Use a <> instead of a planning list variable, -if you need any of the following planning techniques: - -- <> or <>, -- xref:optimization-algorithms/exhaustive-search.adoc#exhaustiveSearch[exhaustive search], -- xref:enterprise-edition/enterprise-edition.adoc#partitionedSearch[partitioned search], -- coexistence with another list variable. +Primitive types (such as ``int``) are not allowed. ==== -For example, the vehicle routing problem can be modeled as follows: - -image::quickstart/vehicle-routing/vehicleRoutingClassDiagramAnnotated.png[] +A planning value range is the set of possible planning values for a planning variable. +Planning value ranges need to come from a finite collection. -This model is closer to the reality than the chained model. -Each vehicle has a list of customers to go to in the order given by the list. -And indeed, the object model matches the natural language description of the problem: -[tabs] -==== -Java:: -+ -[source,java,options="nowrap"] ----- -@PlanningEntity -class Vehicle { +[#planningValueRangeProvider] +=== Planning value range provider - int capacity; - Depot depot; - @PlanningListVariable - List customers = new ArrayList<>(); -} ----- +[#planningValueRangeProviderOverview] +==== Overview +The value range of a planning variable is defined with the `@ValueRangeProvider` annotation. +A `@ValueRangeProvider` may carry a property ``id``, which is referenced by the ``@PlanningVariable``'s property ``valueRangeProviderRefs``. -==== +This annotation can be located on two types of methods: -Planning list variable can be used if the domain meets the following criteria: +* On the Solution: All planning entities share the same value range. +* On the planning entity: The value range differs per planning entity. This is less common. -. There is a one-to-many relationship between the planning entity and the planning value. -. The order in which planning values are assigned to an entity's list variable is significant. +[NOTE] +==== +A `@ValueRangeProvider` annotation needs to be on a member +in a class with a `@PlanningSolution` or a `@PlanningEntity` annotation. +It is ignored on parent classes or subclasses without those annotations. +==== -. Each planning value is assigned to exactly one planning entity. -No planning value may appear in multiple entities. +The return type of that method can be three types: +* ``Collection``: The value range is defined by a `Collection` (usually a ``List``) of its possible values. +* Array: The value range is defined by an array of its possible values. +* ``CountableValueRange``: The value range is defined by its bounds. This is less common. -[#planningListVariableAllowingUnassigned] -=== Allowing unassigned values +[#valueRangeProviderOnSolution] +==== `ValueRangeProvider` on the solution -By default, all planning values have to be assigned to exactly one list variable across the entire planning model. -In an xref:responding-to-change/responding-to-change.adoc#overconstrainedPlanning[over-constrained use case], -this can be counterproductive. -For example: in task assignment with too many tasks for the workforce, -we would rather leave low priority tasks unassigned instead of assigning them to an overloaded worker. +All instances of the same planning entity class share the same set of possible planning values for that planning variable. +This is the most common way to configure a value range. -To allow a planning value to be unassigned, set `allowsUnassignedValues` to ``true``: +The `@PlanningSolution` implementation has a method that returns a `Collection` (or a ``CountableValueRange``). +Any value from that `Collection` is a possible planning value for this planning variable. [tabs] ==== @@ -1151,76 +1143,60 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningListVariable(allowsUnassignedValues = true) -public List getCustomers() { - return customers; +@PlanningVariable +public Timeslot getTimeslot() { + return timeslot; +} +---- ++ +[source,java,options="nowrap"] +---- +@PlanningSolution +public class Timetable { + ... + + @ValueRangeProvider + public List getTimeslots() { + return timeslots; + } + } ---- ==== [IMPORTANT] ==== -Constraint Streams filter out unassigned planning values by default. -Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingUnassigned()] to avoid such unwanted behaviour. -Using a planning list variable with unassigned values implies -that your score calculation is responsible for punishing (or even rewarding) these unassigned values. - -Failure to penalize unassigned values can cause a solution with *all* values unassigned to be the best solution. -See the xref:responding-to-change/responding-to-change.adoc#overconstrainedPlanningWithNullValues[overconstrained planning with `null` variable values] section in the docs for more infomation. +That `Collection` (or ``CountableValueRange``) must not contain the value ``null``, +not even for a <>. ==== -xref:responding-to-change/responding-to-change.adoc[Repeated planning] -(especially xref:responding-to-change/responding-to-change.adoc#realTimePlanning[real-time planning]) -does not mix well with a planning list variable that allows unassigned values. -Every time the Solver starts or a problem fact change is made, -the xref:optimization-algorithms/construction-heuristics.adoc#constructionHeuristics[Construction Heuristics] -will try to initialize all the `null` variables again, which can be a huge waste of time. -One way to deal with this is to filter the entity selector of the placer in the construction heuristic. +xref:using-timefold-solver/configuration.adoc#annotationAlternatives[Annotating the field] instead of the property works too: -[source,xml,options="nowrap"] +[tabs] +==== +Java:: ++ +[source,java,options="nowrap"] ---- - - ... - - - - ... - - - ... - - - +@PlanningSolution +public class Timetable { ... - - ... - ----- - -[#listVariableShadowVariables] -=== List variable shadow variables + @ValueRangeProvider + private List timeslots; -When the planning entity uses a <>, -you can use the following built-in annotations to derive shadow variables from that genuine planning variable. +} +---- -- xref:listVariableShadowVariablesInverseRelation[@InverseRelationShadowVariable]: Used to get the planning entity containing the planning variable list to which a planning value is assigned; -- xref:listVariableShadowVariablesIndex[@IndexShadowVariable]: Used to get the index of a planning value's position it's assigned planning variable list; -- xref:listVariableShadowVariablesPreviousAndNext[@PreviousElementShadowVariable]: Used to get a planning value's predecessor in its assigned planning variable list; -- xref:listVariableShadowVariablesPreviousAndNext[@NextElementShadowVariable]: Used to get a planning value's successor in its assigned planning variable list; -- xref:tailChainVariable[@CascadingUpdateShadowVariable]: Used to update a set of connected elements; -If the built-in shadow variable annotations are insufficient, xref:customShadowVariable[@ShadowVariable] can be used to create custom handlers. +==== -[#listVariableShadowVariablesInverseRelation] -==== Inverse relation shadow variable -Use the `@InverseRelationShadowVariable` annotation to establish bi-directional relationship between the entity and the elements assigned to its list variable. -The type of the inverse shadow variable is the planning entity itself -because there is a one-to-many relationship between the entity and the element classes. +[#valueRangeProviderOnPlanningEntity] +==== `ValueRangeProvider` on the Planning Entity -The planning entity side has a genuine list variable: +Each planning entity has its own value range (a set of possible planning values) for the planning variable. +For example, if a teacher can *never* teach in a room that does not belong to their department, lectures of that teacher can limit their room value range to the rooms of their department. [tabs] ==== @@ -1228,55 +1204,65 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Vehicle { - - @PlanningListVariable - public List getCustomers() { - return customers; + @PlanningVariable + public Room getRoom() { + return room; } - public void setCustomers(List customers) {...} -} + @ValueRangeProvider + public List getPossibleRoomList() { + return getCourse().getTeacher().getDepartment().getRoomList(); + } ---- +==== +Never use this to enforce a soft constraint (or even a hard constraint when the problem might not have a feasible solution). For example: __Unless there is no other way__, a teacher cannot teach in a room that does not belong to their department. +In this case, the teacher should _not_ be limited in their room value range (because sometimes there is no other way). +[NOTE] +==== +By limiting the value range specifically of one planning entity, you are effectively creating a __built-in hard constraint__. +This can have the benefit of severely lowering the number of possible solutions; however, it can also take away the freedom of the optimization algorithms to temporarily break that constraint in order to escape from a local optimum. ==== -On the element side: +A planning entity should _not_ use other planning entities to determine its value range. +That would only try to make the planning entity solve the planning problem itself and interfere with the optimization algorithms. -- Annotate the class with `@PlanningEntity` to make it a shadow planning entity. -- <>, otherwise Timefold Solver won't detect it and the shadow variable won't update. -- Create a property with the genuine planning entity type. -- Annotate it with `@InverseRelationShadowVariable` and set `sourceVariableName` to the name of the genuine planning list variable. +Every entity has its own `List` instance, unless multiple entities have the same value range. +For example, if teacher A and B belong to the same department, they use the same `List` instance. +Furthermore, each `List` contains a subset of the same set of planning value instances. +For example, if department A and B can both use room X, then their `List` instances contain the same `Room` instance. -[tabs] +[NOTE] +==== +A `ValueRangeProvider` on the planning entity consumes more memory than `ValueRangeProvider` on the Solution and disables certain automatic performance optimizations. ==== -Java:: -+ -[source,java,options="nowrap"] ----- -@PlanningEntity -public class Customer { - - @InverseRelationShadowVariable(sourceVariableName = "customers") - public Vehicle getVehicle() { - return vehicle; - } - public void setVehicle(Vehicle vehicle) {...} -} ----- +[WARNING] +==== +A `ValueRangeProvider` on the planning entity is not currently compatible with a <> variable. ==== -[#listVariableShadowVariablesIndex] -==== Index shadow variable +[#referencingValueRangeProviders] +==== Referencing ``ValueRangeProvider``s + +There are two ways how to match a planning variable to a value range provider. +The simplest way is to have value range provider auto-detected. +Another way is to explicitly reference the value range provider. + +[#anonymousValueRangeProviders] +===== Anonymous ``ValueRangeProvider``s -While the `@InverseRelationShadowVariable` allows to establish the bi-directional relationship between the entity -and the elements assigned to its list variable, -`@IndexShadowVariable` provides a pointer into the entity's list variable where the element is assigned. +We already described the first approach. +By not providing any `valueRangeProviderRefs` on the `@PlanningVariable` annotation, +Timefold Solver will go over every ``@ValueRangeProvider``-annotated method or field which does not have an ``id`` property set, +and will match planning variables with value ranges where their types match. -The planning entity side has a genuine list variable: +In the following example, +the planning variable ``car`` will be matched to the value range returned by ``getCompanyCarList()``, +as they both use the ``Car`` type. +It will not match ``getPersonalCarList()``, +because that value range provider is not anonymous; it specifies an ``id``. [tabs] ==== @@ -1284,29 +1270,31 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Vehicle { + @PlanningVariable + public Car getCar() { + return car; + } - @PlanningListVariable - public List getCustomers() { - return customers; + @ValueRangeProvider + public List getCompanyCarList() { + return companyCarList; } - public void setCustomers(List customers) {...} -} + @ValueRangeProvider(id = "personalCarRange") + public List getPersonalCarList() { + return personalCarList; + } ---- ==== -On the element side: - -- Annotate the class with `@PlanningEntity` to make it a shadow planning entity. -- <>, -otherwise Timefold Solver won't detect it and the shadow variable won't update. -- Create a property which returns an `Integer`. -`Integer` is required instead of `int`, as the index may be `null` if the element is not yet assigned to the list variable. -- Annotate it with `@IndexShadowVariable` and set `sourceVariableName` to the name of the genuine planning list variable. +Automatic matching also accounts for polymorphism. +In the following example, +the planning variable ``car`` will be matched to ``getCompanyCarList()`` and ``getPersonalCarList()``, +as both ``CompanyCar`` and ``PersonalCar`` are ``Car``s. +It will not match ``getAirplanes()``, +as an ``Airplane`` is not a ``Car``. [tabs] ==== @@ -1314,27 +1302,36 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Customer { + @PlanningVariable + public Car getCar() { + return car; + } - @IndexShadowVariable(sourceVariableName = "customers") - public Integer getIndexInVehicle() { - return indexInVehicle; + @ValueRangeProvider + public List getCompanyCarList() { + return companyCarList; } -} + @ValueRangeProvider + public List getPersonalCarList() { + return personalCarList; + } + + @ValueRangeProvider + public List getAirplanes() { + return airplaneList; + } ---- ==== -[#listVariableShadowVariablesPreviousAndNext] -==== Previous and next element shadow variable - -Use `@PreviousElementShadowVariable` or `@NextElementShadowVariable` to get a reference to an element that is assigned to the same entity's list variable one index lower (previous element) or one index higher (next element). +[#explicitlyReferencingValueRangeProviders] +===== Explicitly referenced ``ValueRangeProvider``s -NOTE: The previous and next element shadow variables may be `null` even in a fully initialized solution. -The first element's previous shadow variable is `null` and the last element's next shadow variable is `null`. +In more complicated cases where auto-detection is not sufficient or where clarity is preferred over simplicity, +value range providers can also be referenced explicitly. -The planning entity side has a genuine list variable: +In the following example, +the ``car`` planning variable will only be matched to value range provided by methods ``getCompanyCarList()``. [tabs] ==== @@ -1342,22 +1339,26 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Vehicle { + @PlanningVariable(valueRangeProviderRefs = {"companyCarRange"}) + public Car getCar() { + return car; + } - @PlanningListVariable - public List getCustomers() { - return customers; + @ValueRangeProvider(id = "companyCarRange") + public List getCompanyCarList() { + return companyCarList; } - public void setCustomers(List customers) {...} -} + @ValueRangeProvider(id = "personalCarRange") + public List getPersonalCarList() { + return personalCarList; + } ---- ==== -On the element side: +Explicitly referenced value range providers can also be combined, for example: [tabs] ==== @@ -1365,36 +1366,28 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Customer { - - @PreviousElementShadowVariable(sourceVariableName = "customers") - public Customer getPreviousCustomer() { - return previousCustomer; + @PlanningVariable(valueRangeProviderRefs = { "companyCarRange", "personalCarRange" }) + public Car getCar() { + return car; } - public void setPreviousCustomer(Customer previousCustomer) {...} - - @NextElementShadowVariable(sourceVariableName = "customers") - public Customer getNextCustomer() { - return nextCustomer; + @ValueRangeProvider(id = "companyCarRange") + public List getCompanyCarList() { + return companyCarList; } - public void setNextCustomer(Customer nextCustomer) {...} + @ValueRangeProvider(id = "personalCarRange") + public List getPersonalCarList() { + return personalCarList; + } ---- ==== -[#tailChainVariable] -=== Updating tail chains -The annotation `@CascadingUpdateShadowVariable` enables updates a set of connected elements. -Timefold Solver triggers a user-defined logic after all events are processed. -Hence, the related listener is the final one executed during the event lifecycle. -Moreover, -it automatically propagates changes to the subsequent elements in the list -when the value of the related shadow variable changes. +[#valueRangeFactory] +==== `ValueRangeFactory` -The planning entity side has a genuine list variable: +Instead of a ``Collection``, you can also return ``CountableValueRange``, built by the ``ValueRangeFactory``: [tabs] ==== @@ -1402,22 +1395,18 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Vehicle { - - @PlanningListVariable - public List getCustomers() { - return customers; + @ValueRangeProvider + public CountableValueRange getDelayRange() { + return ValueRangeFactory.createIntValueRange(0, 5000); } - - public void setCustomers(List customers) {...} -} ---- ==== +A `CountableValueRange` uses far less memory, because it only holds the bounds. +In the example above, a `Collection` would need to hold all `5000` ints, instead of just the two bounds. -On the element side: +Furthermore, an `incrementUnit` can be specified, for example if you have to buy stocks in units of 200 pieces: [tabs] ==== @@ -1425,76 +1414,112 @@ Java:: + [source,java,options="nowrap"] ---- -@PlanningEntity -public class Customer { + @ValueRangeProvider + public CountableValueRange getStockAmountRange() { + // Range: 0, 200, 400, 600, ..., 9999600, 9999800, 10000000 + return ValueRangeFactory.createIntValueRange(0, 10000000, 200); + } +---- +==== - @InverseRelationShadowVariable(sourceVariableName = "customers") - private Vehicle vehicle; - @PreviousElementShadowVariable(sourceVariableName = "customers") - private Customer previousCustomer; - @CascadingUpdateShadowVariable(targetMethodName = "updateArrivalTime") - private LocalDateTime arrivalTime; +The `ValueRangeFactory` has creation methods for several value class types: - ... +* ``boolean``: A boolean range. +* ``int``: A 32bit integer range. +* ``long``: A 64bit integer range. +* ``BigInteger``: An arbitrary-precision integer range. +* ``BigDecimal``: A decimal point range. By default, the increment unit is the lowest non-zero value in the scale of the bounds. +* `Temporal` (such as ``LocalDate``, ``LocalDateTime``, ...): A time range. - public void updateArrivalTime() {...} ----- -==== -The `targetMethodName` refers to the user-defined logic that updates the annotated shadow variable. -The method must be implemented in the defining entity class, be non-static, and not include any parameters. +[#planningValueSorting] +=== Planning value sorting -In the previous example, -the cascade update listener calls `updateArrivalTime` after all shadow variables have been updated, -including `vehicle` and `previousCustomer`. -It then automatically calls `updateArrivalTime` for the subsequent customers -and stops when the `arrivalTime` value does not change after running target method -or when it reaches the end. +Some optimization algorithms work a bit more efficiently +if the planning values are sorted according to a given metric, +which means they are more likely to satisfy a planning entity requirement. +For example: in bin packing bigger containers are more likely to fit an item. -[WARNING] +[NOTE] ==== -A user-defined logic can only change shadow variables. -Changing a genuine planning variable or a problem fact will result in score corruption. +*Do not try to use planning value order to implement a business constraint.* +It will not affect the score function: if we have infinite solving time, the returned solution will be the same. + +To affect the score function, xref:constraints-and-score/overview.adoc#formalizeTheBusinessConstraints[add a score constraint]. +Only consider adding planning value sort order if it can make the solver more efficient. ==== +To allow the heuristics to take advantage of that domain specific information, +set a `comparatorClass` to the `@PlanningVariable` annotation: + +[source,java,options="nowrap"] +---- + @PlanningVariable(..., comparatorClass = VehicleComparator.class) + public Vehicle getVehicle() { + return vehicle; + } +---- + +[source,java,options="nowrap"] +---- +public class VehicleComparator implements Comparator { + + public int compare(Vehicle a, Vehicle b) { + return new CompareToBuilder() + .append(a.getCapacity(), b.getCapacity()) + .append(a.getId(), b.getId()) + .toComparison(); + } + +} +---- + [NOTE] ==== -When distinct target methods are used by separate `@CascadingUpdateShadowVariable` variables in the same model, -the order of their execution is undefined. +If you have multiple planning value classes in the _same_ value range, +the `comparatorClass` needs to implement a `Comparator` of a common superclass (for example ``Comparator``) +and be able to handle comparing instances of those different classes. ==== -==== Multiple sources +If the model uses a xref:using-timefold-solver/modeling-planning-problems#planningListVariable[list variable], +the setting process is similar, +and the property `comparatorClass` must be specified in the `@PlanningListVariable` annotation: -If the user-defined logic requires updating multiple shadow variables, -apply the `@CascadingUpdateShadowVariable` to all shadow variables. +[source,java,options="nowrap"] +---- + @PlanningListVariable(..., comparatorClass = CustomerComparator.class) + List customers = new ArrayList<>(); +---- -[tabs] -==== -Java:: -+ [source,java,options="nowrap"] ---- -@PlanningEntity -public class Customer { +public class CustomerComparator implements Comparator { - @PreviousElementShadowVariable(sourceVariableName = "customers") - private Customer previousCustomer; - @NextElementShadowVariable(sourceVariableName = "customers") - private Customer nextCustomer; - @CascadingUpdateShadowVariable(targetMethodName = "updateWeightAndArrivalTime") - private LocalDateTime arrivalTime; - @CascadingUpdateShadowVariable(targetMethodName = "updateWeightAndArrivalTime") - private Integer weightAtVisit; - ... + public int compare(Customer a, Customer b) { + return new CompareToBuilder() + .append(a.getPriority(), b.getPriority()) + .append(a.getId(), b.getId()) + .toComparison(); + } - public void updateWeightAndArrivalTime() {...} +} ---- +Alternatively, +you can also set a `comparatorFactoryClass` to the `@PlanningVariable` or `@PlanningListVariable` annotations, +so you have access to the rest of the problem facts from the solution too. + +See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] for more information. +[IMPORTANT] +==== +Values should be sorted in ascending order: "weaker" values are lower, and "stronger" values are higher. +In bin packing, small container < medium container < big container. ==== -Timefold Solver triggers the user-defined logic in `updateWeightAndArrivalTime` at the end of the event lifecycle. -It stops when both `arrivalTime` and `weightAtVisit` values do not change or when it reaches the end. +_None of the current planning variable state in any of the planning entities should be used to compare planning values._ +During construction heuristics, those variables are likely to be ``null``. +For example, none of the `timeslot` variables of any `Lesson` may be used to determine the order of a ``Timeslot``. [#chainedPlanningVariable] == Chained planning variable (TSP, VRP, ...) From 50c166418ac43f51fbbfb9c280216ff015e49f79 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 21 Oct 2025 09:00:15 -0300 Subject: [PATCH 21/33] chore: migration recipes --- .../api/domain/entity/PlanningEntity.java | 14 +++- .../api/domain/variable/PlanningVariable.java | 14 +++- .../resources/META-INF/rewrite/ToLatest.yml | 83 +++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index 57e58067cf..29a0e7ad8a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -92,7 +92,12 @@ interface NullPinningFilter extends PinningFilter { */ Class comparatorClass() default NullComparator.class; - /** Workaround for annotation limitation in {@link #difficultyComparatorClass()}. */ + /** + * Workaround for annotation limitation in {@link #difficultyComparatorClass()}. + * + * @deprecated Deprecated in favor of {@link NullComparator}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") interface NullDifficultyComparator extends NullComparator { } @@ -122,7 +127,12 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */ + /** + * Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. + * + * @deprecated Deprecated in favor of {@link NullComparatorFactory}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") interface NullDifficultyWeightFactory extends NullComparatorFactory { } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 9d3e81ccad..6e5af325f2 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -101,7 +101,12 @@ */ Class comparatorClass() default NullComparator.class; - /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */ + /** + * Workaround for annotation limitation in {@link #strengthComparatorClass()}. + * + * @deprecated Deprecated in favor of {@link NullComparator}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") interface NullStrengthComparator extends NullComparator { } @@ -131,7 +136,12 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */ + /** + * Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. + * + * @deprecated Deprecated in favor of {@link NullComparatorFactory}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") interface NullStrengthWeightFactory extends NullComparatorFactory { } diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index 0e07a9ea49..52654bfea0 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -42,5 +42,88 @@ recipeList: oldFullyQualifiedTypeName: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.ComparatorFactory ignoreDefinition: true + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: ai.timefold.solver.core.api.domain.variable.PlanningVariable + oldAttributeName: strengthComparatorClass + newAttributeName: comparatorClass + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: ai.timefold.solver.core.api.domain.variable.PlanningVariable + oldAttributeName: strengthWeightFactoryClass + newAttributeName: comparatorFactoryClass + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: ai.timefold.solver.core.api.domain.entity.PlanningEntity + oldAttributeName: difficultyComparatorClass + newAttributeName: comparatorClass + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: ai.timefold.solver.core.api.domain.entity.PlanningEntity + oldAttributeName: difficultyWeightFactoryClass + newAttributeName: comparatorFactoryClass + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthComparator + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparator + ignoreDefinition: true + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthWeightFactory + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory + ignoreDefinition: true + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyComparator + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparator + ignoreDefinition: true + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyWeightFactory + newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparatorFactory + ignoreDefinition: true + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig getSorterWeightFactoryClass(..) + newMethodName: getSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig setSorterWeightFactoryClass(..) + newMethodName: setSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig withSorterWeightFactoryClass(..) + newMethodName: withSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig getSorterWeightFactoryClass(..) + newMethodName: getSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig setSorterWeightFactoryClass(..) + newMethodName: setSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig withSorterWeightFactoryClass(..) + newMethodName: withSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig getSorterWeightFactoryClass(..) + newMethodName: getSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig setSorterWeightFactoryClass(..) + newMethodName: setSorterComparatorFactoryClass + - org.openrewrite.java.ChangeMethodName: + methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig withSorterWeightFactoryClass(..) + newMethodName: withSorterComparatorFactoryClass + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING + ignoreDefinition: true + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING_IF_AVAILABLE + ignoreDefinition: true + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING + ignoreDefinition: true + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING_IF_AVAILABLE + ignoreDefinition: true + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING + ignoreDefinition: true + - org.openrewrite.java.ReplaceConstantWithAnotherConstant: + existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE + fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING_IF_AVAILABLE + ignoreDefinition: true - org.openrewrite.java.RemoveUnusedImports - ai.timefold.solver.migration.ChangeVersion From 477f4e71b36c7e9167add33a76d037263282f72a Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 21 Oct 2025 09:04:10 -0300 Subject: [PATCH 22/33] chore: address comments --- .../heuristic/selector/list/DestinationSelectorFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java index a1f85efb32..731e1e66c8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java @@ -47,7 +47,8 @@ public DestinationSelector buildDestinationSelector(HeuristicConfigPo // Solution-range model entitySelectorConfig.setCacheType(SelectionCacheType.PHASE); } else { - // Entity-range model requires the sorting to be done in each step + // The entity-range model requires sorting at each step + // because the list of reachable entities can vary from one entity to another entitySelectorConfig.setCacheType(SelectionCacheType.STEP); } entitySelectorConfig.setSelectionOrder(SelectionOrder.SORTED); From 01b59686dc7feae2aaf3b54edab00ef5469cb915 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 21 Oct 2025 09:56:29 -0300 Subject: [PATCH 23/33] docs: minor changes --- .../optimization-algorithms/construction-heuristics.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc index 4d139cbf33..99e6497b4a 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc @@ -172,7 +172,7 @@ For a very advanced configuration, see <> and <>. -It sorts the planning entities in descending order and the planning values in ascending order based on specified metrics. +It sorts the planning entities in descending order and the planning values in ascending order based on defined metrics. Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting] @@ -272,7 +272,7 @@ For a very advanced configuration, see <> and <>. -It sorts the planning entities and the planning values in descending order based on specified metrics. +It sorts the planning entities and the planning values in descending order based on defined metrics. Requires the model to support xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntitySorting[planning entity sorting] From f1c8911ee70ac52fa8490eabc3c6440a4a612d44 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 21 Oct 2025 14:30:07 -0300 Subject: [PATCH 24/33] chore: address comments --- .../api/domain/entity/PlanningEntity.java | 64 +++++++-------- .../api/domain/variable/PlanningVariable.java | 79 +++++++++---------- 2 files changed, 71 insertions(+), 72 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index 29a0e7ad8a..7ecf4da8c1 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -34,6 +34,37 @@ @Retention(RUNTIME) public @interface PlanningEntity { + /** + * Allows sorting a collection of planning entities for this variable. + * Some algorithms perform better when the entities are sorted based on specific metrics. + *

+ * The {@link Comparator} should sort the data in ascending order. + * For example, prioritize three vehicles by sorting them based on their capacity: + * Vehicle C (4 people), Vehicle A (6 people), Vehicle B (32 people) + *

+ * Do not use together with {@link #comparatorFactoryClass()}. + * + * @return {@link PlanningVariable.NullComparator} when it is null (workaround for annotation limitation) + * @see #comparatorFactoryClass() + */ + Class comparatorClass() default NullComparator.class; + + interface NullComparator extends Comparator { + } + + /** + * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. + *

+ * Do not use together with {@link #comparatorClass()}. + * + * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) + * @see #comparatorClass() + */ + Class comparatorFactoryClass() default NullComparatorFactory.class; + + interface NullComparatorFactory extends ComparatorFactory { + } + /** * A pinned planning entity is never changed during planning, * this is useful in repeated planning use cases (such as continuous planning and real-time planning). @@ -50,7 +81,7 @@ /** * Workaround for annotation limitation in {@link #pinningFilter()}. - * + * * @deprecated Prefer using {@link PlanningPin}. */ @Deprecated(forRemoval = true, since = "1.23.0") @@ -77,21 +108,6 @@ interface NullPinningFilter extends PinningFilter { @Deprecated(forRemoval = true, since = "1.28.0") Class difficultyComparatorClass() default NullDifficultyComparator.class; - /** - * Allows sorting a collection of planning entities for this variable. - * Some algorithms perform better when the entities are sorted based on specific metrics. - *

- * The {@link Comparator} should sort the data in ascending order. - * For example, prioritize three vehicles by sorting them based on their capacity: - * Vehicle C (4 people), Vehicle A (6 people), Vehicle B (32 people) - *

- * Do not use together with {@link #comparatorFactoryClass()}. - * - * @return {@link PlanningVariable.NullComparator} when it is null (workaround for annotation limitation) - * @see #comparatorFactoryClass() - */ - Class comparatorClass() default NullComparator.class; - /** * Workaround for annotation limitation in {@link #difficultyComparatorClass()}. * @@ -101,9 +117,6 @@ interface NullPinningFilter extends PinningFilter { interface NullDifficultyComparator extends NullComparator { } - interface NullComparator extends Comparator { - } - /** * The {@link ComparatorFactory} alternative for {@link #difficultyComparatorClass()}. *

@@ -117,16 +130,6 @@ interface NullComparator extends Comparator { @Deprecated(forRemoval = true, since = "1.28.0") Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; - /** - * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. - *

- * Do not use together with {@link #comparatorClass()}. - * - * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) - * @see #comparatorClass() - */ - Class comparatorFactoryClass() default NullComparatorFactory.class; - /** * Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. * @@ -136,7 +139,4 @@ interface NullComparator extends Comparator { interface NullDifficultyWeightFactory extends NullComparatorFactory { } - interface NullComparatorFactory extends ComparatorFactory { - } - } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 6e5af325f2..70391dd81f 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -50,6 +50,45 @@ */ boolean allowsUnassigned() default false; + /** + * In some use cases, such as Vehicle Routing, planning entities form a specific graph type, + * as specified by {@link PlanningVariableGraphType}. + * + * @return never null, defaults to {@link PlanningVariableGraphType#NONE} + */ + PlanningVariableGraphType graphType() default PlanningVariableGraphType.NONE; + + /** + * Allows sorting a collection of planning values for this variable. + * Some algorithms perform better when the values are sorted based on specific metrics. + *

+ * The {@link Comparator} should sort the data in ascending order. + * For example, prioritize three visits by sorting them based on their importance: + * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY) + *

+ * Do not use together with {@link #comparatorFactoryClass()}. + * + * @return {@link NullComparator} when it is null (workaround for annotation limitation) + * @see #comparatorFactoryClass() + */ + Class comparatorClass() default NullComparator.class; + + interface NullComparator extends Comparator { + } + + /** + * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. + *

+ * Do not use together with {@link #comparatorClass()}. + * + * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) + * @see #comparatorClass() + */ + Class comparatorFactoryClass() default NullComparatorFactory.class; + + interface NullComparatorFactory extends ComparatorFactory { + } + /** * As defined by {@link #allowsUnassigned()}. * @@ -59,14 +98,6 @@ @Deprecated(forRemoval = true, since = "1.8.0") boolean nullable() default false; - /** - * In some use cases, such as Vehicle Routing, planning entities form a specific graph type, - * as specified by {@link PlanningVariableGraphType}. - * - * @return never null, defaults to {@link PlanningVariableGraphType#NONE} - */ - PlanningVariableGraphType graphType() default PlanningVariableGraphType.NONE; - /** * Allows a collection of planning values for this variable to be sorted by strength. * A strengthWeight estimates how strong a planning value is. @@ -86,21 +117,6 @@ @Deprecated(forRemoval = true, since = "1.28.0") Class strengthComparatorClass() default NullStrengthComparator.class; - /** - * Allows sorting a collection of planning values for this variable. - * Some algorithms perform better when the values are sorted based on specific metrics. - *

- * The {@link Comparator} should sort the data in ascending order. - * For example, prioritize three visits by sorting them based on their importance: - * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY) - *

- * Do not use together with {@link #comparatorFactoryClass()}. - * - * @return {@link NullComparator} when it is null (workaround for annotation limitation) - * @see #comparatorFactoryClass() - */ - Class comparatorClass() default NullComparator.class; - /** * Workaround for annotation limitation in {@link #strengthComparatorClass()}. * @@ -110,9 +126,6 @@ interface NullStrengthComparator extends NullComparator { } - interface NullComparator extends Comparator { - } - /** * The {@link ComparatorFactory} alternative for {@link #strengthComparatorClass()}. *

@@ -126,16 +139,6 @@ interface NullComparator extends Comparator { @Deprecated(forRemoval = true, since = "1.28.0") Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; - /** - * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. - *

- * Do not use together with {@link #comparatorClass()}. - * - * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) - * @see #comparatorClass() - */ - Class comparatorFactoryClass() default NullComparatorFactory.class; - /** * Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. * @@ -144,8 +147,4 @@ interface NullComparator extends Comparator { @Deprecated(forRemoval = true, since = "1.28.0") interface NullStrengthWeightFactory extends NullComparatorFactory { } - - interface NullComparatorFactory extends ComparatorFactory { - } - } From 9c0b87aa11322cc501ed847013cb8d8effce64fc Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 09:12:50 -0300 Subject: [PATCH 25/33] chore: address comments --- benchmark/src/main/resources/benchmark.xsd | 15 ++- core/src/build/revapi-differences.json | 48 +++++----- .../selector/entity/EntitySelectorConfig.java | 64 ++++++++++--- .../selector/move/MoveSelectorConfig.java | 64 ++++++++++--- .../selector/value/ValueSelectorConfig.java | 64 ++++++++++--- .../SelectionSorterWeightFactory.java | 3 - .../entity/EntitySelectorFactory.java | 92 +++++++++++------- .../move/AbstractMoveSelectorFactory.java | 89 +++++++++++------- .../selector/value/ValueSelectorFactory.java | 93 ++++++++++++------- core/src/main/resources/solver.xsd | 12 ++- ...DefaultConstructionHeuristicPhaseTest.java | 85 ++++++++++++++--- .../entity/EntitySelectorFactoryTest.java | 25 ++++- .../move/MoveSelectorFactoryTest.java | 59 +++++++++--- .../decorator/SortingMoveSelectorTest.java | 15 ++- .../ChangeMoveSelectorFactoryTest.java | 4 +- .../value/ValueSelectorFactoryTest.java | 26 +++++- ...ataObjectSortableDescendingComparator.java | 20 ++++ ...tdataObjectSortableDescendingFactory.java} | 3 +- .../optimization-algorithms/overview.adoc | 4 +- .../resources/META-INF/rewrite/ToLatest.yml | 18 ++-- .../src/test/resources/solver-full.xml | 10 +- 21 files changed, 583 insertions(+), 230 deletions(-) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java rename core/src/test/java/ai/timefold/solver/core/testdomain/common/{TestdataObjectSortableFactory.java => TestdataObjectSortableDescendingFactory.java} (84%) diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index 084a742e2a..86ede21c93 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -890,10 +890,13 @@ + + + - + @@ -1085,10 +1088,13 @@ + + + - + @@ -1235,10 +1241,13 @@ + + + - + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index df27fb07fa..092fe759a2 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -364,6 +364,17 @@ "parameterIndex": "0", "justification": "New comparator factory class" }, + { + "ignore": true, + "code": "java.annotation.attributeValueChanged", + "old": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "new": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "annotationType": "jakarta.xml.bind.annotation.XmlType", + "attribute": "propOrder", + "oldValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "newValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"comparatorClass\", \"sorterWeightFactoryClass\", \"comparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "justification": "New comparator properties" + }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", @@ -379,6 +390,17 @@ "parameterIndex": "0", "justification": "New comparator factory class" }, + { + "ignore": true, + "code": "java.annotation.attributeValueChanged", + "old": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "annotationType": "jakarta.xml.bind.annotation.XmlType", + "attribute": "propOrder", + "oldValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", + "newValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"comparatorClass\", \"sorterWeightFactoryClass\", \"comparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", + "justification": "New comparator properties" + }, { "ignore": true, "code": "java.method.returnTypeTypeParametersChanged", @@ -394,17 +416,6 @@ "parameterIndex": "0", "justification": "New comparator factory class" }, - { - "ignore": true, - "code": "java.annotation.attributeValueChanged", - "old": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", - "new": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", - "annotationType": "jakarta.xml.bind.annotation.XmlType", - "attribute": "propOrder", - "oldValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", - "newValue": "{\"id\", \"mimicSelectorRef\", \"entityClass\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", - "justification": "New comparator factory field" - }, { "ignore": true, "code": "java.annotation.attributeValueChanged", @@ -413,19 +424,8 @@ "annotationType": "jakarta.xml.bind.annotation.XmlType", "attribute": "propOrder", "oldValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", - "newValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", - "justification": "New comparator factory field" - }, - { - "ignore": true, - "code": "java.annotation.attributeValueChanged", - "old": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", - "new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", - "annotationType": "jakarta.xml.bind.annotation.XmlType", - "attribute": "propOrder", - "oldValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", - "newValue": "{\"cacheType\", \"selectionOrder\", \"filterClass\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterComparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\", \"fixedProbabilityWeight\"}", - "justification": "New comparator factory field" + "newValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"comparatorClass\", \"sorterWeightFactoryClass\", \"comparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", + "justification": "New comparator properties" } ] } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index ff8c198dc1..d00b4c9dcc 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -33,8 +33,9 @@ "filterClass", "sorterManner", "sorterComparatorClass", + "comparatorClass", "sorterWeightFactoryClass", - "sorterComparatorFactoryClass", + "comparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -63,13 +64,18 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe protected Class filterClass = null; protected EntitySorterManner sorterManner = null; + /** + * @deprecated Deprecated in favor of {@link #comparatorClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterComparatorClass = null; + protected Class comparatorClass = null; /** - * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; - protected Class sorterComparatorFactoryClass = null; + protected Class comparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -154,16 +160,32 @@ public void setSorterManner(@Nullable EntitySorterManner sorterManner) { this.sorterManner = sorterManner; } + /** + * @deprecated Deprecated in favor of {@link #getComparatorClass()} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterComparatorClass() { return sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterComparatorClass(@Nullable Class sorterComparatorClass) { this.sorterComparatorClass = sorterComparatorClass; } + public Class getComparatorClass() { + return comparatorClass; + } + + public void setComparatorClass(Class comparatorClass) { + this.comparatorClass = comparatorClass; + } + /** - * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { @@ -171,7 +193,7 @@ public void setSorterComparatorClass(@Nullable Class sorte } /** - * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)} * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -179,12 +201,12 @@ public void setSorterWeightFactoryClass(@Nullable Class getSorterComparatorFactoryClass() { - return sorterComparatorFactoryClass; + public Class getComparatorFactoryClass() { + return comparatorFactoryClass; } - public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { - this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + public void setComparatorFactoryClass(Class comparatorFactoryClass) { + this.comparatorFactoryClass = comparatorFactoryClass; } public @Nullable SelectionSorterOrder getSorterOrder() { @@ -264,13 +286,22 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { return this; } + /** + * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull EntitySelectorConfig withSorterComparatorClass(@NonNull Class comparatorClass) { this.setSorterComparatorClass(comparatorClass); return this; } + public @NonNull EntitySelectorConfig withComparatorClass(@NonNull Class comparatorClass) { + this.setComparatorClass(comparatorClass); + return this; + } + /** - * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)} * @param weightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -281,8 +312,8 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull EntitySelectorConfig - withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { - this.setSorterComparatorFactoryClass(comparatorFactoryClass); + withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setComparatorFactoryClass(comparatorFactoryClass); return this; } @@ -327,10 +358,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { sorterManner, inheritedConfig.getSorterManner()); sorterComparatorClass = ConfigUtils.inheritOverwritableProperty( sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); + comparatorClass = ConfigUtils.inheritOverwritableProperty( + comparatorClass, inheritedConfig.getComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); - sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( - sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); + comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( @@ -355,8 +388,9 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) { } classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); + classVisitor.accept(comparatorClass); classVisitor.accept(sorterWeightFactoryClass); - classVisitor.accept(sorterComparatorFactoryClass); + classVisitor.accept(comparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 729c1478b3..029e79bf99 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -67,8 +67,9 @@ "selectionOrder", "filterClass", "sorterComparatorClass", + "comparatorClass", "sorterWeightFactoryClass", - "sorterComparatorFactoryClass", + "comparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -82,13 +83,18 @@ public abstract class MoveSelectorConfig filterClass = null; + /** + * @deprecated Deprecated in favor of {@link #comparatorClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterComparatorClass = null; + protected Class comparatorClass = null; /** - * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; - protected Class sorterComparatorFactoryClass = null; + protected Class comparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -126,16 +132,32 @@ public void setFilterClass(@Nullable Class filterClas this.filterClass = filterClass; } + /** + * @deprecated Deprecated in favor of {@link #getComparatorClass()} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterComparatorClass() { return sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterComparatorClass(@Nullable Class sorterComparatorClass) { this.sorterComparatorClass = sorterComparatorClass; } + public Class getComparatorClass() { + return comparatorClass; + } + + public void setComparatorClass(Class comparatorClass) { + this.comparatorClass = comparatorClass; + } + /** - * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { @@ -143,7 +165,7 @@ public void setSorterComparatorClass(@Nullable Class sorte } /** - * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)} * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -151,12 +173,12 @@ public void setSorterWeightFactoryClass(@Nullable Class getSorterComparatorFactoryClass() { - return sorterComparatorFactoryClass; + public Class getComparatorFactoryClass() { + return comparatorFactoryClass; } - public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { - this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + public void setComparatorFactoryClass(Class comparatorFactoryClass) { + this.comparatorFactoryClass = comparatorFactoryClass; } public @Nullable SelectionSorterOrder getSorterOrder() { @@ -219,13 +241,22 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { return (Config_) this; } + /** + * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull Config_ withSorterComparatorClass(@NonNull Class sorterComparatorClass) { this.sorterComparatorClass = sorterComparatorClass; return (Config_) this; } + public @NonNull Config_ withComparatorClass(@NonNull Class comparatorClass) { + this.setComparatorClass(comparatorClass); + return (Config_) this; + } + /** - * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)} * @param sorterWeightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -236,8 +267,8 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { } public @NonNull Config_ - withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { - this.setSorterComparatorFactoryClass(comparatorFactoryClass); + withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setComparatorFactoryClass(comparatorFactoryClass); return (Config_) this; } @@ -292,8 +323,9 @@ public void inheritFolded(@NonNull MoveSelectorConfig foldedConfig) { protected void visitCommonReferencedClasses(@NonNull Consumer> classVisitor) { classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); + classVisitor.accept(comparatorClass); classVisitor.accept(sorterWeightFactoryClass); - classVisitor.accept(sorterComparatorFactoryClass); + classVisitor.accept(comparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } @@ -304,10 +336,12 @@ private void inheritCommon(MoveSelectorConfig inheritedConfig) { filterClass = ConfigUtils.inheritOverwritableProperty(filterClass, inheritedConfig.getFilterClass()); sorterComparatorClass = ConfigUtils.inheritOverwritableProperty( sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); + comparatorClass = ConfigUtils.inheritOverwritableProperty( + comparatorClass, inheritedConfig.getComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); - sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( - sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); + comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index d999476c2e..e688753b44 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -34,8 +34,9 @@ "filterClass", "sorterManner", "sorterComparatorClass", + "comparatorClass", "sorterWeightFactoryClass", - "sorterComparatorFactoryClass", + "comparatorFactoryClass", "sorterOrder", "sorterClass", "probabilityWeightFactoryClass", @@ -61,13 +62,18 @@ public class ValueSelectorConfig extends SelectorConfig { protected Class filterClass = null; protected ValueSorterManner sorterManner = null; + /** + * @deprecated Deprecated in favor of {@link #comparatorClass}. + */ + @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterComparatorClass = null; + protected Class comparatorClass = null; /** - * @deprecated Deprecated in favor of {@link #sorterComparatorFactoryClass}. + * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") protected Class sorterWeightFactoryClass = null; - protected Class sorterComparatorFactoryClass = null; + protected Class comparatorFactoryClass = null; protected SelectionSorterOrder sorterOrder = null; protected Class sorterClass = null; @@ -160,16 +166,32 @@ public void setSorterManner(@Nullable ValueSorterManner sorterManner) { this.sorterManner = sorterManner; } + /** + * @deprecated Deprecated in favor of {@link #getComparatorClass()} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterComparatorClass() { return sorterComparatorClass; } + /** + * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public void setSorterComparatorClass(@Nullable Class sorterComparatorClass) { this.sorterComparatorClass = sorterComparatorClass; } + public Class getComparatorClass() { + return comparatorClass; + } + + public void setComparatorClass(Class comparatorClass) { + this.comparatorClass = comparatorClass; + } + /** - * @deprecated Deprecated in favor of {@link #getSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") public @Nullable Class getSorterWeightFactoryClass() { @@ -177,7 +199,7 @@ public void setSorterComparatorClass(@Nullable Class sorte } /** - * @deprecated Deprecated in favor of {@link #setSorterComparatorFactoryClass} + * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)} * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -185,12 +207,12 @@ public void setSorterWeightFactoryClass(@Nullable Class getSorterComparatorFactoryClass() { - return sorterComparatorFactoryClass; + public Class getComparatorFactoryClass() { + return comparatorFactoryClass; } - public void setSorterComparatorFactoryClass(Class sorterComparatorFactoryClass) { - this.sorterComparatorFactoryClass = sorterComparatorFactoryClass; + public void setComparatorFactoryClass(Class comparatorFactoryClass) { + this.comparatorFactoryClass = comparatorFactoryClass; } public @Nullable SelectionSorterOrder getSorterOrder() { @@ -275,13 +297,22 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { return this; } + /** + * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} + */ + @Deprecated(forRemoval = true, since = "1.28.0") public @NonNull ValueSelectorConfig withSorterComparatorClass(@NonNull Class comparatorClass) { this.setSorterComparatorClass(comparatorClass); return this; } + public @NonNull ValueSelectorConfig withComparatorClass(@NonNull Class comparatorClass) { + this.setComparatorClass(comparatorClass); + return this; + } + /** - * @deprecated Deprecated in favor of {@link #withSorterComparatorFactoryClass(Class)} + * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)} * @param weightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") @@ -292,8 +323,8 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } public @NonNull ValueSelectorConfig - withSorterComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { - this.setSorterComparatorFactoryClass(comparatorFactoryClass); + withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + this.setComparatorFactoryClass(comparatorFactoryClass); return this; } @@ -338,10 +369,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { sorterManner, inheritedConfig.getSorterManner()); sorterComparatorClass = ConfigUtils.inheritOverwritableProperty( sorterComparatorClass, inheritedConfig.getSorterComparatorClass()); + comparatorClass = ConfigUtils.inheritOverwritableProperty( + comparatorClass, inheritedConfig.getComparatorClass()); sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty( sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass()); - sorterComparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( - sorterComparatorFactoryClass, inheritedConfig.getSorterComparatorFactoryClass()); + comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty( + comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass()); sorterOrder = ConfigUtils.inheritOverwritableProperty( sorterOrder, inheritedConfig.getSorterOrder()); sorterClass = ConfigUtils.inheritOverwritableProperty( @@ -366,8 +399,9 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) { } classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); + classVisitor.accept(comparatorClass); classVisitor.accept(sorterWeightFactoryClass); - classVisitor.accept(sorterComparatorFactoryClass); + classVisitor.accept(comparatorFactoryClass); classVisitor.accept(sorterClass); classVisitor.accept(probabilityWeightFactoryClass); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index c82fda6972..dca0bbc505 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -6,8 +6,6 @@ import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.Selector; -import org.jspecify.annotations.NullMarked; - /** * Creates a weight to decide the order of a collections of selections * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}). @@ -24,7 +22,6 @@ * @param the selection type */ @Deprecated(forRemoval = true, since = "1.28.0") -@NullMarked public interface SelectionSorterWeightFactory extends ComparatorFactory { Comparable createSorterWeight(Solution_ solution, T selection); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 63c4f6373d..8903f0141d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -142,7 +142,7 @@ protected EntitySelector buildMimicReplaying(HeuristicConfigPolicy determineComparatorClass(EntitySelectorConfig entitySelectorConfig) { + var propertyName = determineComparatorPropertyName(entitySelectorConfig); + if (propertyName.equals("sorterComparatorClass")) { + return entitySelectorConfig.getSorterComparatorClass(); + } else { + return entitySelectorConfig.getComparatorClass(); + } + } + + private static String determineComparatorFactoryPropertyName(EntitySelectorConfig entitySelectorConfig) { var weightFactoryClass = entitySelectorConfig.getSorterWeightFactoryClass(); - var comparatorFactoryClass = entitySelectorConfig.getSorterComparatorFactoryClass(); + var comparatorFactoryClass = entitySelectorConfig.getComparatorFactoryClass(); if (weightFactoryClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( "The entitySelectorConfig (%s) cannot have a %s (%s) and %s (%s) at the same time.".formatted( entitySelectorConfig, "sorterWeightFactoryClass", weightFactoryClass, - "sorterComparatorFactoryClass", comparatorFactoryClass)); + "comparatorFactoryClass", comparatorFactoryClass)); } - return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; + return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } private static Class - determineSorterComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { - var propertyName = determineSorterComparatorFactoryPropertyName(entitySelectorConfig); + determineComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { + var propertyName = determineComparatorFactoryPropertyName(entitySelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return entitySelectorConfig.getSorterWeightFactoryClass(); } else { - return entitySelectorConfig.getSorterComparatorFactoryClass(); + return entitySelectorConfig.getComparatorFactoryClass(); } } @@ -274,36 +295,36 @@ private EntitySelector applyFiltering(EntitySelector entit protected void validateSorting(SelectionOrder resolvedSelectionOrder) { var sorterManner = config.getSorterManner(); - var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); - var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); + var comparatorClass = determineComparatorClass(config); + var comparatorPropertyName = determineComparatorPropertyName(config); + var comparatorFactoryPropertyName = determineComparatorFactoryPropertyName(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); var sorterOrder = config.getSorterOrder(); var sorterClass = config.getSorterClass(); - if ((sorterManner != null || sorterComparatorClass != null || sorterComparatorFactoryClass != null + if ((sorterManner != null || comparatorClass != null || comparatorFactoryClass != null || sorterOrder != null || sorterClass != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { throw new IllegalArgumentException(""" The entitySelectorConfig (%s) with sorterManner (%s) \ - and sorterComparatorClass (%s) and sorterWeightFactoryClass (%s) and sorterOrder (%s) and sorterClass (%s) \ + and %s (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) \ has a resolvedSelectionOrder (%s) that is not %s.""" - .formatted(config, sorterManner, sorterComparatorClass, sorterComparatorFactoryClass, sorterOrder, - sorterClass, - resolvedSelectionOrder, SelectionOrder.SORTED)); + .formatted(config, sorterManner, comparatorPropertyName, comparatorClass, comparatorFactoryPropertyName, + comparatorFactoryClass, sorterOrder, sorterClass, resolvedSelectionOrder, SelectionOrder.SORTED)); } - assertNotSorterMannerAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, - EntitySelectorFactory::determineSorterComparatorFactoryClass); + assertNotSorterMannerAnd(config, comparatorPropertyName, EntitySelectorFactory::determineComparatorClass); + assertNotSorterMannerAnd(config, comparatorFactoryPropertyName, + EntitySelectorFactory::determineComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", EntitySelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); - assertNotSorterClassAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, - EntitySelectorFactory::determineSorterComparatorFactoryClass); + assertNotSorterClassAnd(config, comparatorPropertyName, EntitySelectorFactory::determineComparatorClass); + assertNotSorterClassAnd(config, comparatorFactoryPropertyName, + EntitySelectorFactory::determineComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); - if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { + if (comparatorClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( - "The entitySelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s)." - .formatted(config, sorterComparatorClass, sorterComparatorFactoryPropertyName, - sorterComparatorFactoryClass)); + "The entitySelectorConfig (%s) has both a %s (%s) and a %s (%s)." + .formatted(config, comparatorPropertyName, comparatorClass, comparatorFactoryPropertyName, + comparatorFactoryClass)); } } @@ -333,34 +354,35 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; var sorterManner = config.getSorterManner(); - var comparatorFactoryClass = determineSorterComparatorFactoryClass(config); + var comparatorClass = determineComparatorClass(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); if (sorterManner != null) { var entityDescriptor = entitySelector.getEntityDescriptor(); if (!EntitySelectorConfig.hasSorter(sorterManner, entityDescriptor)) { return entitySelector; } sorter = EntitySelectorConfig.determineSorter(sorterManner, entityDescriptor); - } else if (config.getSorterComparatorClass() != null) { + } else if (comparatorClass != null) { Comparator sorterComparator = - instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); + instanceCache.newInstance(config, determineComparatorPropertyName(config), comparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, comparatorFactoryPropertyName, comparatorFactoryClass); + instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + comparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { - var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); throw new IllegalArgumentException(""" The entitySelectorConfig (%s) with resolvedSelectionOrder (%s) needs \ - a sorterManner (%s) or a sorterComparatorClass (%s) or a %s (%s) \ + a sorterManner (%s) or a %s (%s) or a %s (%s) \ or a sorterClass (%s).""" - .formatted(config, resolvedSelectionOrder, sorterManner, config.getSorterComparatorClass(), - comparatorFactoryPropertyName, comparatorFactoryClass, config.getSorterClass())); + .formatted(config, resolvedSelectionOrder, sorterManner, determineComparatorPropertyName(config), + comparatorClass, determineComparatorFactoryPropertyName(config), comparatorFactoryClass, + config.getSorterClass())); } entitySelector = new SortingEntitySelector<>(entitySelector, resolvedCacheType, sorter); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 7b98ef8fdc..797b0efb94 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -120,25 +120,46 @@ protected boolean determineBaseRandomSelection(SelectionCacheType resolvedCacheT }; } - private String determineSorterComparatorFactoryPropertyName(MoveSelectorConfig_ moveSelectorConfig) { + private String determineComparatorPropertyName(MoveSelectorConfig_ moveSelectorConfig) { + var sorterComparatorClass = moveSelectorConfig.getSorterComparatorClass(); + var comparatorClass = moveSelectorConfig.getComparatorClass(); + if (sorterComparatorClass != null && comparatorClass != null) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) cannot have a %s (%s) and %s (%s) at the same time.".formatted( + moveSelectorConfig, "sorterComparatorClass", sorterComparatorClass, + "comparatorClass", comparatorClass)); + } + return sorterComparatorClass != null ? "sorterComparatorClass" : "comparatorClass"; + } + + private Class determineComparatorClass(MoveSelectorConfig_ moveSelectorConfig) { + var propertyName = determineComparatorPropertyName(moveSelectorConfig); + if (propertyName.equals("sorterComparatorClass")) { + return moveSelectorConfig.getSorterComparatorClass(); + } else { + return moveSelectorConfig.getComparatorClass(); + } + } + + private String determineComparatorFactoryPropertyName(MoveSelectorConfig_ moveSelectorConfig) { var weightFactoryClass = moveSelectorConfig.getSorterWeightFactoryClass(); - var comparatorFactoryClass = moveSelectorConfig.getSorterComparatorFactoryClass(); + var comparatorFactoryClass = moveSelectorConfig.getComparatorFactoryClass(); if (weightFactoryClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( "The moveSelectorConfig (%s) cannot have a %s (%s) and %s (%s) at the same time.".formatted( moveSelectorConfig, "sorterWeightFactoryClass", weightFactoryClass, - "sorterComparatorFactoryClass", comparatorFactoryClass)); + "comparatorFactoryClass", comparatorFactoryClass)); } - return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; + return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } private Class - determineSorterComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { - var propertyName = determineSorterComparatorFactoryPropertyName(moveSelectorConfig); + determineComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { + var propertyName = determineComparatorFactoryPropertyName(moveSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return moveSelectorConfig.getSorterWeightFactoryClass(); } else { - return moveSelectorConfig.getSorterComparatorFactoryClass(); + return moveSelectorConfig.getComparatorFactoryClass(); } } @@ -171,32 +192,36 @@ private MoveSelector applyFiltering(MoveSelector moveSelec } protected void validateSorting(SelectionOrder resolvedSelectionOrder) { - var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); - var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); - if ((config.getSorterComparatorClass() != null || sorterComparatorFactoryClass != null + var comparatorClass = determineComparatorClass(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); + if ((comparatorClass != null || comparatorFactoryClass != null || config.getSorterOrder() != null || config.getSorterClass() != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { throw new IllegalArgumentException( - "The moveSelectorConfig (%s) with sorterComparatorClass (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) has a resolvedSelectionOrder (%s) that is not %s." - .formatted(config, config.getSorterComparatorClass(), sorterComparatorFactoryPropertyName, - sorterComparatorFactoryClass, config.getSorterOrder(), config.getSorterClass(), + "The moveSelectorConfig (%s) with %s (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) has a resolvedSelectionOrder (%s) that is not %s." + .formatted(config, determineComparatorPropertyName(config), comparatorClass, + determineComparatorFactoryPropertyName(config), + comparatorFactoryClass, config.getSorterOrder(), config.getSorterClass(), resolvedSelectionOrder, SelectionOrder.SORTED)); } - if (config.getSorterComparatorClass() != null && sorterComparatorFactoryClass != null) { + if (comparatorClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( - "The moveSelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s).".formatted(config, - config.getSorterComparatorClass(), sorterComparatorFactoryPropertyName, - sorterComparatorFactoryClass)); + "The moveSelectorConfig (%s) has both a %s (%s) and a %s (%s).".formatted(config, + determineComparatorPropertyName(config), comparatorClass, + determineComparatorFactoryPropertyName(config), + comparatorFactoryClass)); } - if (config.getSorterComparatorClass() != null && config.getSorterClass() != null) { + if (comparatorClass != null && config.getSorterClass() != null) { throw new IllegalArgumentException( - "The moveSelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterClass (%s)." - .formatted(config, config.getSorterComparatorClass(), config.getSorterClass())); + "The moveSelectorConfig (%s) has both a %s (%s) and a sorterClass (%s)." + .formatted(config, determineComparatorPropertyName(config), comparatorClass, + config.getSorterClass())); } - if (sorterComparatorFactoryClass != null && config.getSorterClass() != null) { + if (comparatorFactoryClass != null && config.getSorterClass() != null) { throw new IllegalArgumentException( "The moveSelectorConfig (%s) has both a %s (%s) and a sorterClass (%s).".formatted(config, - sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass, config.getSorterClass())); + determineComparatorFactoryPropertyName(config), comparatorFactoryClass, + config.getSorterClass())); } if (config.getSorterClass() != null && config.getSorterOrder() != null) { throw new IllegalArgumentException( @@ -209,27 +234,27 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT SelectionOrder resolvedSelectionOrder, MoveSelector moveSelector) { if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter> sorter; - var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); - var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); + var comparatorClass = determineComparatorClass(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); var sorterClass = config.getSorterClass(); - if (sorterComparatorClass != null) { + if (comparatorClass != null) { Comparator> sorterComparator = - ConfigUtils.newInstance(config, "sorterComparatorClass", sorterComparatorClass); + ConfigUtils.newInstance(config, determineComparatorPropertyName(config), comparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (sorterComparatorFactoryClass != null) { + } else if (comparatorFactoryClass != null) { ComparatorFactory> comparatorFactory = - ConfigUtils.newInstance(config, sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass); + ConfigUtils.newInstance(config, determineComparatorFactoryPropertyName(config), + comparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); } else { throw new IllegalArgumentException( - "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a sorterComparatorClass (%s) or a %s (%s) or a sorterClass (%s)." - .formatted(config, resolvedSelectionOrder, sorterComparatorClass, - sorterComparatorFactoryPropertyName, sorterComparatorFactoryClass, + "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a %s (%s) or a %s (%s) or a sorterClass (%s)." + .formatted(config, resolvedSelectionOrder, determineComparatorPropertyName(config), + comparatorClass, determineComparatorFactoryPropertyName(config), comparatorFactoryClass, sorterClass)); } moveSelector = new SortingMoveSelector<>(moveSelector, resolvedCacheType, sorter); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index a3a226a607..1b06063fed 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -159,8 +159,8 @@ protected ValueSelector buildMimicReplaying(HeuristicConfigPolicy determineComparatorClass(ValueSelectorConfig valueSelectorConfig) { + var propertyName = determineComparatorPropertyName(valueSelectorConfig); + if (propertyName.equals("sorterComparatorClass")) { + return valueSelectorConfig.getSorterComparatorClass(); + } else { + return valueSelectorConfig.getComparatorClass(); + } + } + + private static String determineComparatorFactoryPropertyName(ValueSelectorConfig valueSelectorConfig) { var weightFactoryClass = valueSelectorConfig.getSorterWeightFactoryClass(); - var comparatorFactoryClass = valueSelectorConfig.getSorterComparatorFactoryClass(); + var comparatorFactoryClass = valueSelectorConfig.getComparatorFactoryClass(); if (weightFactoryClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( "The valueSelectorConfig (%s) cannot have a %s (%s) and %s (%s) at the same time.".formatted( valueSelectorConfig, "sorterWeightFactoryClass", weightFactoryClass, - "sorterComparatorFactoryClass", comparatorFactoryClass)); + "comparatorFactoryClass", comparatorFactoryClass)); } - return weightFactoryClass != null ? "sorterWeightFactoryClass" : "sorterComparatorFactoryClass"; + return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } private static Class - determineSorterComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { - var propertyName = determineSorterComparatorFactoryPropertyName(valueSelectorConfig); + determineComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { + var propertyName = determineComparatorFactoryPropertyName(valueSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return valueSelectorConfig.getSorterWeightFactoryClass(); } else { - return valueSelectorConfig.getSorterComparatorFactoryClass(); + return valueSelectorConfig.getComparatorFactoryClass(); } } @@ -293,35 +314,36 @@ protected ValueSelector applyInitializedChainedValueFilter(HeuristicC protected void validateSorting(SelectionOrder resolvedSelectionOrder) { var sorterManner = config.getSorterManner(); - var sorterComparatorClass = config.getSorterComparatorClass(); - var sorterComparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); - var sorterComparatorFactoryClass = determineSorterComparatorFactoryClass(config); + var comparatorPropertyName = determineComparatorPropertyName(config); + var comparatorClass = determineComparatorClass(config); + var comparatorFactoryPropertyName = determineComparatorFactoryPropertyName(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); var sorterOrder = config.getSorterOrder(); var sorterClass = config.getSorterClass(); - if ((sorterManner != null || sorterComparatorClass != null || sorterComparatorFactoryClass != null + if ((sorterManner != null || comparatorClass != null || comparatorFactoryClass != null || sorterOrder != null || sorterClass != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { throw new IllegalArgumentException(""" The valueSelectorConfig (%s) with sorterManner (%s) \ - and sorterComparatorClass (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) \ + and %s (%s) and %s (%s) and sorterOrder (%s) and sorterClass (%s) \ has a resolvedSelectionOrder (%s) that is not %s.""" - .formatted(config, sorterManner, sorterComparatorClass, sorterComparatorFactoryPropertyName, - sorterComparatorFactoryClass, sorterOrder, sorterClass, resolvedSelectionOrder, + .formatted(config, sorterManner, comparatorPropertyName, comparatorClass, comparatorFactoryPropertyName, + comparatorFactoryClass, sorterOrder, sorterClass, resolvedSelectionOrder, SelectionOrder.SORTED)); } - assertNotSorterMannerAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterMannerAnd(config, sorterComparatorFactoryPropertyName, - ValueSelectorFactory::determineSorterComparatorFactoryClass); + assertNotSorterMannerAnd(config, comparatorPropertyName, ValueSelectorFactory::determineComparatorClass); + assertNotSorterMannerAnd(config, comparatorFactoryPropertyName, + ValueSelectorFactory::determineComparatorFactoryClass); assertNotSorterMannerAnd(config, "sorterClass", ValueSelectorConfig::getSorterClass); assertNotSorterMannerAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); - assertNotSorterClassAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); - assertNotSorterClassAnd(config, sorterComparatorFactoryPropertyName, - ValueSelectorFactory::determineSorterComparatorFactoryClass); + assertNotSorterClassAnd(config, comparatorPropertyName, ValueSelectorFactory::determineComparatorClass); + assertNotSorterClassAnd(config, comparatorFactoryPropertyName, + ValueSelectorFactory::determineComparatorFactoryClass); assertNotSorterClassAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); - if (sorterComparatorClass != null && sorterComparatorFactoryClass != null) { + if (comparatorClass != null && comparatorFactoryClass != null) { throw new IllegalArgumentException( - "The valueSelectorConfig (%s) has both a sorterComparatorClass (%s) and a %s (%s)." - .formatted(config, sorterComparatorClass, sorterComparatorFactoryPropertyName, - sorterComparatorFactoryClass)); + "The valueSelectorConfig (%s) has both a %s (%s) and a %s (%s)." + .formatted(config, comparatorPropertyName, comparatorClass, comparatorFactoryPropertyName, + comparatorFactoryClass)); } } @@ -351,34 +373,35 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; var sorterManner = config.getSorterManner(); - var comparatorFactoryClass = determineSorterComparatorFactoryClass(config); + var comparatorClass = determineComparatorClass(config); + var comparatorFactoryClass = determineComparatorFactoryClass(config); if (sorterManner != null) { var variableDescriptor = valueSelector.getVariableDescriptor(); if (!ValueSelectorConfig.hasSorter(sorterManner, variableDescriptor)) { return valueSelector; } sorter = ValueSelectorConfig.determineSorter(sorterManner, variableDescriptor); - } else if (config.getSorterComparatorClass() != null) { + } else if (comparatorClass != null) { Comparator sorterComparator = - instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); + instanceCache.newInstance(config, determineComparatorPropertyName(config), comparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, comparatorFactoryPropertyName, comparatorFactoryClass); + instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + comparatorFactoryClass); sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { - var comparatorFactoryPropertyName = determineSorterComparatorFactoryPropertyName(config); throw new IllegalArgumentException(""" The valueSelectorConfig (%s) with resolvedSelectionOrder (%s) needs \ - a sorterManner (%s) or a sorterComparatorClass (%s) or a %s (%s) \ + a sorterManner (%s) or a %s (%s) or a %s (%s) \ or a sorterClass (%s).""" - .formatted(config, resolvedSelectionOrder, sorterManner, config.getSorterComparatorClass(), - comparatorFactoryPropertyName, comparatorFactoryClass, config.getSorterClass())); + .formatted(config, resolvedSelectionOrder, sorterManner, determineComparatorPropertyName(config), + comparatorClass, determineComparatorFactoryPropertyName(config), comparatorFactoryClass, + config.getSorterClass())); } if (!valueSelector.getVariableDescriptor().canExtractValueRangeFromSolution() && resolvedCacheType == SelectionCacheType.STEP) { diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index af7582dec4..8c6e02fc1c 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -399,9 +399,11 @@ + + - + @@ -529,9 +531,11 @@ + + - + @@ -629,9 +633,11 @@ + + - + 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 d13b6e9e07..b9a8538595 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 @@ -33,7 +33,8 @@ import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.common.DummyHardSoftEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableFactory; +import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableDescendingComparator; +import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableDescendingFactory; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; @@ -1237,13 +1238,13 @@ private static List generateEntityFactorySortin .withId("sortedEntitySelector") .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterWeightFactoryClass(TestdataObjectSortableFactory.class)) + .withSorterWeightFactoryClass(TestdataObjectSortableDescendingFactory.class)) .withValueSelectorConfig( new ValueSelectorConfig() .withMimicSelectorRef("sortedValueSelector")) .withValueSelectorConfig(new ValueSelectorConfig()))), new int[] { 2, 1, 0 }, - // Only entities are sorted + // Only entities are sorted in descending order false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() @@ -1254,13 +1255,47 @@ private static List generateEntityFactorySortin .withId("sortedEntitySelector") .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterComparatorFactoryClass(TestdataObjectSortableFactory.class)) + .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)) .withValueSelectorConfig( new ValueSelectorConfig() .withMimicSelectorRef("sortedValueSelector")) .withValueSelectorConfig(new ValueSelectorConfig()))), new int[] { 2, 1, 0 }, - // Only entities are sorted + // Only entities are sorted in descending order + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withId("sortedValueSelector")) + .withMoveSelectorConfig(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterComparatorClass(TestdataObjectSortableDescendingComparator.class)) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withValueSelectorConfig(new ValueSelectorConfig()))), + new int[] { 2, 1, 0 }, + // Only entities are sorted in descending order + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedValuePlacerConfig() + .withValueSelectorConfig(new ValueSelectorConfig().withId("sortedValueSelector")) + .withMoveSelectorConfig(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig(new EntitySelectorConfig() + .withId("sortedEntitySelector") + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withComparatorClass(TestdataObjectSortableDescendingComparator.class)) + .withValueSelectorConfig( + new ValueSelectorConfig() + .withMimicSelectorRef("sortedValueSelector")) + .withValueSelectorConfig(new ValueSelectorConfig()))), + new int[] { 2, 1, 0 }, + // Only entities are sorted in descending order false)); return values; } @@ -1286,7 +1321,7 @@ void solveEntityFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(TestdataObjectSortableFactory.extractCode(entity.getValueList().get(0).getCode())) + assertThat(TestdataObjectSortableDescendingFactory.extractCode(entity.getValueList().get(0).getCode())) .isEqualTo(phaseConfig.expected[i]); } } @@ -1304,9 +1339,37 @@ private static List generateValueFactorySorting .withValueSelectorConfig(new ValueSelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterWeightFactoryClass(TestdataObjectSortableFactory.class)))), + .withSorterWeightFactoryClass(TestdataObjectSortableDescendingFactory.class)))), + new int[] { 2, 1, 0 }, + // Only values are sorted in descending order + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig().withId("sortedEntitySelector")) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig(new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)))), + new int[] { 2, 1, 0 }, + // Only values are sorted in descending order + false)); + values.add(new ConstructionHeuristicTestConfig( + new ConstructionHeuristicPhaseConfig() + .withEntityPlacerConfig(new QueuedEntityPlacerConfig() + .withEntitySelectorConfig(new EntitySelectorConfig().withId("sortedEntitySelector")) + .withMoveSelectorConfigs(new ChangeMoveSelectorConfig() + .withEntitySelectorConfig( + new EntitySelectorConfig().withMimicSelectorRef("sortedEntitySelector")) + .withValueSelectorConfig(new ValueSelectorConfig() + .withSelectionOrder(SelectionOrder.SORTED) + .withCacheType(SelectionCacheType.PHASE) + .withSorterComparatorClass(TestdataObjectSortableDescendingComparator.class)))), new int[] { 2, 1, 0 }, - // Only values are sorted + // Only values are sorted in descending order false)); values.add(new ConstructionHeuristicTestConfig( new ConstructionHeuristicPhaseConfig() @@ -1318,9 +1381,9 @@ private static List generateValueFactorySorting .withValueSelectorConfig(new ValueSelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withSorterComparatorFactoryClass(TestdataObjectSortableFactory.class)))), + .withComparatorClass(TestdataObjectSortableDescendingComparator.class)))), new int[] { 2, 1, 0 }, - // Only values are sorted + // Only values are sorted in descending order false)); return values; } @@ -1346,7 +1409,7 @@ void solveValueFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValue()).isNotNull(); - assertThat(TestdataObjectSortableFactory.extractCode(entity.getValue().getCode())) + assertThat(TestdataObjectSortableDescendingFactory.extractCode(entity.getValue().getCode())) .isEqualTo(phaseConfig.expected[i]); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 616195349d..508addf5ae 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -196,13 +196,32 @@ void failFast_ifMimicRecordingIsUsedWithOtherProperty() { } @Test - void failFast_ifBothFactoriesUsed() { + void failFast_ifBothComparatorsUsed() { + var entitySelectorConfig = new EntitySelectorConfig() + .withSorterManner(EntitySorterManner.DESCENDING) + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withComparatorClass(DummyEntityComparator.class) + .withSorterComparatorClass(DummyEntityComparator.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> EntitySelectorFactory. create(entitySelectorConfig) + .buildEntitySelector(buildHeuristicConfigPolicy(), SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining("The entitySelectorConfig") + .withMessageContaining( + "cannot have a sorterComparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummyEntityComparator)") + .withMessageContaining( + "and comparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummyEntityComparator) at the same time"); + } + + @Test + void failFast_ifBothComparatorFactoriesUsed() { var entitySelectorConfig = new EntitySelectorConfig() .withSorterManner(EntitySorterManner.DESCENDING) .withCacheType(SelectionCacheType.PHASE) .withSelectionOrder(SelectionOrder.SORTED) .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class) - .withSorterComparatorFactoryClass(DummySelectionComparatorFactory.class); + .withComparatorFactoryClass(DummySelectionComparatorFactory.class); assertThatIllegalArgumentException() .isThrownBy(() -> EntitySelectorFactory. create(entitySelectorConfig) @@ -211,7 +230,7 @@ void failFast_ifBothFactoriesUsed() { .withMessageContaining( "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory)") .withMessageContaining( - "and sorterComparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); + "and comparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); } public static class DummySelectionProbabilityWeightFactory diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index e6c63ee7b4..26234fe5e6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -202,19 +202,34 @@ void applySorting_withoutAnySortingClass() { @Test void applySorting_withSorterComparatorClass() { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setSorterComparatorClass(DummyComparator.class); + // Old setting + { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterComparatorClass(DummyComparator.class); - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = - moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); - assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } + // New setting + { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setComparatorClass(DummyComparator.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } } @Test - void applySorting_withSorterComparatorFactoryClass() { + void applySorting_withComparatorFactoryClass() { // Old setting { final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); @@ -232,7 +247,7 @@ void applySorting_withSorterComparatorFactoryClass() { final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setSorterComparatorFactoryClass(DummyValueFactory.class); + moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); MoveSelector sortingMoveSelector = @@ -280,12 +295,30 @@ public Move doMove(ScoreDirector scoreDirect } @Test - void failFast_ifBothFactoriesUsed() { + void failFast_ifBothComparatorFactoriesUsed() { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterComparatorClass(DummyComparator.class); + moveSelectorConfig.setComparatorClass(DummyComparator.class); + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + assertThatIllegalArgumentException() + .isThrownBy(() -> moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, + baseMoveSelector)) + .withMessageContaining("The moveSelectorConfig") + .withMessageContaining( + "cannot have a sorterComparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelectorFactoryTest$DummyComparator)") + .withMessageContaining( + "and comparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelectorFactoryTest$DummyComparator) at the same time"); + } + + @Test + void failFast_ifBothComparatorsFactoriesUsed() { final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); moveSelectorConfig.setSorterWeightFactoryClass(DummyValueFactory.class); - moveSelectorConfig.setSorterComparatorFactoryClass(DummyValueFactory.class); + moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); assertThatIllegalArgumentException() .isThrownBy(() -> moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, @@ -294,7 +327,7 @@ void failFast_ifBothFactoriesUsed() { .withMessageContaining( "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.testdomain.common.DummyValueFactory)") .withMessageContaining( - "and sorterComparatorFactoryClass (class ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time"); + "and comparatorFactoryClass (class ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time"); } static class DummyMoveSelectorConfig extends MoveSelectorConfig { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index 36b38b6705..942cb72c35 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -66,7 +66,13 @@ private static List generateConfiguration() { .withSorterWeightFactoryClass(TestCodeAssertableComparatorFactory.class), new DummySorterMoveSelectorConfig() .withSorterOrder(SelectionSorterOrder.ASCENDING) - .withSorterComparatorFactoryClass(TestCodeAssertableComparatorFactory.class)); + .withSorterComparatorClass(TestCodeAssertableComparator.class), + new DummySorterMoveSelectorConfig() + .withSorterOrder(SelectionSorterOrder.ASCENDING) + .withComparatorFactoryClass(TestCodeAssertableComparatorFactory.class), + new DummySorterMoveSelectorConfig() + .withSorterOrder(SelectionSorterOrder.ASCENDING) + .withComparatorClass(TestCodeAssertableComparator.class)); } @ParameterizedTest @@ -206,4 +212,11 @@ public Comparable createSorter(Object o, CodeAssertable selection) { } } + public static class TestCodeAssertableComparator implements Comparator { + @Override + public int compare(CodeAssertable o1, CodeAssertable o2) { + return o1.getCode().compareTo(o2.getCode()); + } + } + } 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 9597800e06..54e0e9d19c 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 @@ -125,7 +125,7 @@ void unfoldConfiguredIntoListChangeMoveSelectorConfig() { long selectedCountLimit = 200; ChangeMoveSelectorConfig moveSelectorConfig = new ChangeMoveSelectorConfig() .withEntitySelectorConfig(new EntitySelectorConfig(TestdataListEntity.class) - .withSorterComparatorClass(DummyEntityComparator.class)) + .withComparatorClass(DummyEntityComparator.class)) .withValueSelectorConfig(new ValueSelectorConfig("valueList")) .withCacheType(moveSelectorCacheType) .withSelectionOrder(moveSelectorSelectionOrder) @@ -149,7 +149,7 @@ void unfoldConfiguredIntoListChangeMoveSelectorConfig() { DestinationSelectorConfig destinationSelectorConfig = listChangeMoveSelectorConfig.getDestinationSelectorConfig(); EntitySelectorConfig entitySelectorConfig = destinationSelectorConfig.getEntitySelectorConfig(); assertThat(entitySelectorConfig.getEntityClass()).isEqualTo(TestdataListEntity.class); - assertThat(entitySelectorConfig.getSorterComparatorClass()).isEqualTo(DummyEntityComparator.class); + assertThat(entitySelectorConfig.getComparatorClass()).isEqualTo(DummyEntityComparator.class); ValueSelectorConfig valueSelectorConfig = destinationSelectorConfig.getValueSelectorConfig(); assertThat(valueSelectorConfig.getVariableName()).isEqualTo("valueList"); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 891f591199..7ebd3936ca 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -262,13 +262,33 @@ void failFast_ifMimicRecordingIsUsedWithOtherProperty() { } @Test - void failFast_ifBothFactoriesUsed() { + void failFast_ifBothComparatorsUsed() { + var valueSelectorConfig = new ValueSelectorConfig() + .withSorterManner(ValueSorterManner.DESCENDING) + .withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterComparatorClass(DummyValueComparator.class) + .withComparatorClass(DummyValueComparator.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> ValueSelectorFactory. create(valueSelectorConfig) + .buildValueSelector(buildHeuristicConfigPolicy(), TestdataEntity.buildEntityDescriptor(), + SelectionCacheType.PHASE, SelectionOrder.SORTED)) + .withMessageContaining("The valueSelectorConfig") + .withMessageContaining( + "cannot have a sorterComparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummyValueComparator)") + .withMessageContaining( + "and comparatorClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummyValueComparator) at the same time"); + } + + @Test + void failFast_ifBothComparatorFactoriesUsed() { var valueSelectorConfig = new ValueSelectorConfig() .withSorterManner(ValueSorterManner.DESCENDING) .withCacheType(SelectionCacheType.PHASE) .withSelectionOrder(SelectionOrder.SORTED) .withSorterWeightFactoryClass(DummySelectionComparatorFactory.class) - .withSorterComparatorFactoryClass(DummySelectionComparatorFactory.class); + .withComparatorFactoryClass(DummySelectionComparatorFactory.class); assertThatIllegalArgumentException() .isThrownBy(() -> ValueSelectorFactory. create(valueSelectorConfig) @@ -278,7 +298,7 @@ void failFast_ifBothFactoriesUsed() { .withMessageContaining( "cannot have a sorterWeightFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory)") .withMessageContaining( - "and sorterComparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); + "and comparatorFactoryClass (class ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactoryTest$DummySelectionComparatorFactory) at the same time"); } static Stream applyListValueFiltering() { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java new file mode 100644 index 0000000000..84d894aafd --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java @@ -0,0 +1,20 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.testdomain.TestdataObject; + +public class TestdataObjectSortableDescendingComparator implements Comparator { + + @Override + public int compare(TestdataObject o1, TestdataObject o2) { + // Descending order + return extractCode(o2.getCode()) - extractCode(o1.getCode()); + } + + public static int extractCode(String code) { + var idx = code.lastIndexOf(" "); + return Integer.parseInt(code.substring(idx + 1)); + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java similarity index 84% rename from core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java index cf85000aad..d568641974 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataObjectSortableFactory implements SelectionSorterWeightFactory, +public class TestdataObjectSortableDescendingFactory implements SelectionSorterWeightFactory, ComparatorFactory { @Override @@ -14,6 +14,7 @@ public Comparable createSorterWeight(Object solution, TestdataObject selection) @Override public Comparable createSorter(Object solution, TestdataObject selection) { + // Descending order return -extractCode(selection.getCode()); } diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index a4176282ef..4f82411a68 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1588,7 +1588,7 @@ You'll also need to configure it (unless it's annotated on the domain model and PHASE SORTED - ...VisitComparator + ...VisitComparator DESCENDING ---- @@ -1621,7 +1621,7 @@ You'll also need to configure it (unless it's annotated on the domain model and PHASE SORTED - ...MySorterComparatorFactory + ...MySorterComparatorFactory DESCENDING ---- diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index 52654bfea0..26f658d5d3 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -76,31 +76,31 @@ recipeList: ignoreDefinition: true - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getSorterComparatorFactoryClass + newMethodName: getComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setSorterComparatorFactoryClass + newMethodName: setComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withSorterComparatorFactoryClass + newMethodName: withComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getSorterComparatorFactoryClass + newMethodName: getComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setSorterComparatorFactoryClass + newMethodName: setComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withSorterComparatorFactoryClass + newMethodName: withComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getSorterComparatorFactoryClass + newMethodName: getComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setSorterComparatorFactoryClass + newMethodName: setComparatorFactoryClass - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withSorterComparatorFactoryClass + newMethodName: withComparatorFactoryClass - org.openrewrite.java.ReplaceConstantWithAnotherConstant: existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING diff --git a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml index 3c09f52e7b..de68fe4d71 100644 --- a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml +++ b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml @@ -103,8 +103,8 @@ java.lang.Object NONE - java.lang.Object - java.lang.Object + java.lang.Object + java.lang.Object DESCENDING java.lang.Object java.lang.Object @@ -121,8 +121,8 @@ java.lang.Object INCREASING_STRENGTH - java.lang.Object - java.lang.Object + java.lang.Object + java.lang.Object DESCENDING java.lang.Object java.lang.Object @@ -1147,4 +1147,4 @@ - \ No newline at end of file + From 38c183cd4eb3ac8215dcbb0ccbb0a5cbc9b0ac27 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 09:27:13 -0300 Subject: [PATCH 26/33] chore: address comments --- .../ConstructionHeuristicType.java | 9 +- .../selector/entity/EntitySelectorConfig.java | 80 +++++++++++------- .../selector/move/MoveSelectorConfig.java | 57 ++++++++----- .../selector/value/ValueSelectorConfig.java | 84 +++++++++++-------- .../decorator/FactorySelectionSorter.java | 8 +- 5 files changed, 142 insertions(+), 96 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java index 030c249ddb..aa43642ff8 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java +++ b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java @@ -5,8 +5,9 @@ import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +@NullMarked @XmlEnum public enum ConstructionHeuristicType { /** @@ -56,7 +57,7 @@ public enum ConstructionHeuristicType { */ ALLOCATE_FROM_POOL; - public @NonNull EntitySorterManner getDefaultEntitySorterManner() { + public EntitySorterManner getDefaultEntitySorterManner() { return switch (this) { case FIRST_FIT, WEAKEST_FIT, STRONGEST_FIT -> EntitySorterManner.NONE; case FIRST_FIT_DECREASING, WEAKEST_FIT_DECREASING, STRONGEST_FIT_DECREASING -> EntitySorterManner.DESCENDING; @@ -65,7 +66,7 @@ public enum ConstructionHeuristicType { }; } - public @NonNull ValueSorterManner getDefaultValueSorterManner() { + public ValueSorterManner getDefaultValueSorterManner() { return switch (this) { case FIRST_FIT, FIRST_FIT_DECREASING -> ValueSorterManner.NONE; case WEAKEST_FIT, WEAKEST_FIT_DECREASING -> ValueSorterManner.ASCENDING; @@ -79,7 +80,7 @@ public enum ConstructionHeuristicType { * @return {@link ConstructionHeuristicType#values()} without duplicates (abstract types that end up behaving as one of the * other types). */ - public static @NonNull ConstructionHeuristicType @NonNull [] getBluePrintTypes() { + public static ConstructionHeuristicType [] getBluePrintTypes() { return new ConstructionHeuristicType[] { FIRST_FIT, FIRST_FIT_DECREASING, diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index d00b4c9dcc..38ddada995 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -20,7 +20,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @XmlType(propOrder = { @@ -41,6 +41,7 @@ "probabilityWeightFactoryClass", "selectedCountLimit" }) +@NullMarked public class EntitySelectorConfig extends SelectorConfig { public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRef) { @@ -48,39 +49,54 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe .withMimicSelectorRef(mimicSelectorRef); } + @Nullable @XmlAttribute protected String id = null; @XmlAttribute + @Nullable protected String mimicSelectorRef = null; + @Nullable protected Class entityClass = null; - + @Nullable protected SelectionCacheType cacheType = null; + @Nullable protected SelectionOrder selectionOrder = null; + @Nullable @XmlElement(name = "nearbySelection") protected NearbySelectionConfig nearbySelectionConfig = null; + @Nullable protected Class filterClass = null; + @Nullable protected EntitySorterManner sorterManner = null; /** * @deprecated Deprecated in favor of {@link #comparatorClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterComparatorClass = null; + @Nullable protected Class comparatorClass = null; /** * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterWeightFactoryClass = null; + @Nullable protected Class comparatorFactoryClass = null; + @Nullable protected SelectionSorterOrder sorterOrder = null; + @Nullable protected Class sorterClass = null; + @Nullable protected Class probabilityWeightFactoryClass = null; + @Nullable protected Long selectedCountLimit = null; public EntitySelectorConfig() { @@ -176,11 +192,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public Class getComparatorClass() { + public @Nullable Class getComparatorClass() { return comparatorClass; } - public void setComparatorClass(Class comparatorClass) { + public void setComparatorClass(@Nullable Class comparatorClass) { this.comparatorClass = comparatorClass; } @@ -201,11 +217,11 @@ public void setSorterWeightFactoryClass(@Nullable Class getComparatorFactoryClass() { + public @Nullable Class getComparatorFactoryClass() { return comparatorFactoryClass; } - public void setComparatorFactoryClass(Class comparatorFactoryClass) { + public void setComparatorFactoryClass(@Nullable Class comparatorFactoryClass) { this.comparatorFactoryClass = comparatorFactoryClass; } @@ -246,42 +262,42 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { // With methods // ************************************************************************ - public @NonNull EntitySelectorConfig withId(@NonNull String id) { + public EntitySelectorConfig withId(String id) { this.setId(id); return this; } - public @NonNull EntitySelectorConfig withMimicSelectorRef(@NonNull String mimicSelectorRef) { + public EntitySelectorConfig withMimicSelectorRef(String mimicSelectorRef) { this.setMimicSelectorRef(mimicSelectorRef); return this; } - public @NonNull EntitySelectorConfig withEntityClass(@NonNull Class entityClass) { + public EntitySelectorConfig withEntityClass(Class entityClass) { this.setEntityClass(entityClass); return this; } - public @NonNull EntitySelectorConfig withCacheType(@NonNull SelectionCacheType cacheType) { + public EntitySelectorConfig withCacheType(SelectionCacheType cacheType) { this.setCacheType(cacheType); return this; } - public @NonNull EntitySelectorConfig withSelectionOrder(@NonNull SelectionOrder selectionOrder) { + public EntitySelectorConfig withSelectionOrder(SelectionOrder selectionOrder) { this.setSelectionOrder(selectionOrder); return this; } - public @NonNull EntitySelectorConfig withNearbySelectionConfig(@NonNull NearbySelectionConfig nearbySelectionConfig) { + public EntitySelectorConfig withNearbySelectionConfig(NearbySelectionConfig nearbySelectionConfig) { this.setNearbySelectionConfig(nearbySelectionConfig); return this; } - public @NonNull EntitySelectorConfig withFilterClass(@NonNull Class filterClass) { + public EntitySelectorConfig withFilterClass(Class filterClass) { this.setFilterClass(filterClass); return this; } - public @NonNull EntitySelectorConfig withSorterManner(@NonNull EntitySorterManner sorterManner) { + public EntitySelectorConfig withSorterManner(EntitySorterManner sorterManner) { this.setSorterManner(sorterManner); return this; } @@ -290,12 +306,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull EntitySelectorConfig withSorterComparatorClass(@NonNull Class comparatorClass) { + public EntitySelectorConfig withSorterComparatorClass(Class comparatorClass) { this.setSorterComparatorClass(comparatorClass); return this; } - public @NonNull EntitySelectorConfig withComparatorClass(@NonNull Class comparatorClass) { + public EntitySelectorConfig withComparatorClass(Class comparatorClass) { this.setComparatorClass(comparatorClass); return this; } @@ -305,35 +321,35 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { * @param weightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull EntitySelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + public EntitySelectorConfig + withSorterWeightFactoryClass(Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } - public @NonNull EntitySelectorConfig - withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + public EntitySelectorConfig + withComparatorFactoryClass(Class comparatorFactoryClass) { this.setComparatorFactoryClass(comparatorFactoryClass); return this; } - public @NonNull EntitySelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { + public EntitySelectorConfig withSorterOrder(SelectionSorterOrder sorterOrder) { this.setSorterOrder(sorterOrder); return this; } - public @NonNull EntitySelectorConfig withSorterClass(@NonNull Class sorterClass) { + public EntitySelectorConfig withSorterClass(Class sorterClass) { this.setSorterClass(sorterClass); return this; } - public @NonNull EntitySelectorConfig - withProbabilityWeightFactoryClass(@NonNull Class factoryClass) { + public EntitySelectorConfig + withProbabilityWeightFactoryClass(Class factoryClass) { this.setProbabilityWeightFactoryClass(factoryClass); return this; } - public @NonNull EntitySelectorConfig withSelectedCountLimit(long selectedCountLimit) { + public EntitySelectorConfig withSelectedCountLimit(long selectedCountLimit) { this.setSelectedCountLimit(selectedCountLimit); return this; } @@ -343,7 +359,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { // ************************************************************************ @Override - public @NonNull EntitySelectorConfig inherit(@NonNull EntitySelectorConfig inheritedConfig) { + public EntitySelectorConfig inherit(EntitySelectorConfig inheritedConfig) { id = ConfigUtils.inheritOverwritableProperty(id, inheritedConfig.getId()); mimicSelectorRef = ConfigUtils.inheritOverwritableProperty(mimicSelectorRef, inheritedConfig.getMimicSelectorRef()); @@ -376,12 +392,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } @Override - public @NonNull EntitySelectorConfig copyConfig() { + public EntitySelectorConfig copyConfig() { return new EntitySelectorConfig().inherit(this); } @Override - public void visitReferencedClasses(@NonNull Consumer> classVisitor) { + public void visitReferencedClasses(Consumer> classVisitor) { classVisitor.accept(entityClass); if (nearbySelectionConfig != null) { nearbySelectionConfig.visitReferencedClasses(classVisitor); @@ -400,8 +416,8 @@ public String toString() { return getClass().getSimpleName() + "(" + entityClass + ")"; } - public static boolean hasSorter(@NonNull EntitySorterManner entitySorterManner, - @NonNull EntityDescriptor entityDescriptor) { + public static boolean hasSorter(EntitySorterManner entitySorterManner, + EntityDescriptor entityDescriptor) { return switch (entitySorterManner) { case NONE -> false; case DECREASING_DIFFICULTY, DESCENDING -> true; @@ -410,8 +426,8 @@ public static boolean hasSorter(@NonNull EntitySorterManner entitySo }; } - public static @NonNull SelectionSorter determineSorter( - @NonNull EntitySorterManner entitySorterManner, @NonNull EntityDescriptor entityDescriptor) { + public static SelectionSorter determineSorter( + EntitySorterManner entitySorterManner, EntityDescriptor entityDescriptor) { return switch (entitySorterManner) { case NONE: throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 029e79bf99..4d7454035b 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -35,7 +35,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** @@ -76,32 +76,45 @@ "selectedCountLimit", "fixedProbabilityWeight" }) +@NullMarked public abstract class MoveSelectorConfig> extends SelectorConfig { + @Nullable protected SelectionCacheType cacheType = null; + @Nullable protected SelectionOrder selectionOrder = null; + @Nullable protected Class filterClass = null; /** * @deprecated Deprecated in favor of {@link #comparatorClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterComparatorClass = null; + @Nullable protected Class comparatorClass = null; /** * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterWeightFactoryClass = null; + @Nullable protected Class comparatorFactoryClass = null; + @Nullable protected SelectionSorterOrder sorterOrder = null; + @Nullable protected Class sorterClass = null; + @Nullable protected Class probabilityWeightFactoryClass = null; + @Nullable protected Long selectedCountLimit = null; + @Nullable private Double fixedProbabilityWeight = null; // ************************************************************************ @@ -173,11 +186,11 @@ public void setSorterWeightFactoryClass(@Nullable Class getComparatorFactoryClass() { + public @Nullable Class getComparatorFactoryClass() { return comparatorFactoryClass; } - public void setComparatorFactoryClass(Class comparatorFactoryClass) { + public void setComparatorFactoryClass(@Nullable Class comparatorFactoryClass) { this.comparatorFactoryClass = comparatorFactoryClass; } @@ -226,17 +239,17 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { // With methods // ************************************************************************ - public @NonNull Config_ withCacheType(@NonNull SelectionCacheType cacheType) { + public Config_ withCacheType(SelectionCacheType cacheType) { this.cacheType = cacheType; return (Config_) this; } - public @NonNull Config_ withSelectionOrder(@NonNull SelectionOrder selectionOrder) { + public Config_ withSelectionOrder(SelectionOrder selectionOrder) { this.selectionOrder = selectionOrder; return (Config_) this; } - public @NonNull Config_ withFilterClass(@NonNull Class filterClass) { + public Config_ withFilterClass(Class filterClass) { this.filterClass = filterClass; return (Config_) this; } @@ -245,12 +258,12 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull Config_ withSorterComparatorClass(@NonNull Class sorterComparatorClass) { + public Config_ withSorterComparatorClass(Class sorterComparatorClass) { this.sorterComparatorClass = sorterComparatorClass; return (Config_) this; } - public @NonNull Config_ withComparatorClass(@NonNull Class comparatorClass) { + public Config_ withComparatorClass(Class comparatorClass) { this.setComparatorClass(comparatorClass); return (Config_) this; } @@ -260,40 +273,40 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { * @param sorterWeightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull Config_ withSorterWeightFactoryClass( - @NonNull Class sorterWeightFactoryClass) { + public Config_ withSorterWeightFactoryClass( + Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } - public @NonNull Config_ - withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + public Config_ + withComparatorFactoryClass(Class comparatorFactoryClass) { this.setComparatorFactoryClass(comparatorFactoryClass); return (Config_) this; } - public @NonNull Config_ withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { + public Config_ withSorterOrder(SelectionSorterOrder sorterOrder) { this.sorterOrder = sorterOrder; return (Config_) this; } - public @NonNull Config_ withSorterClass(@NonNull Class sorterClass) { + public Config_ withSorterClass(Class sorterClass) { this.sorterClass = sorterClass; return (Config_) this; } - public @NonNull Config_ withProbabilityWeightFactoryClass( - @NonNull Class probabilityWeightFactoryClass) { + public Config_ withProbabilityWeightFactoryClass( + Class probabilityWeightFactoryClass) { this.probabilityWeightFactoryClass = probabilityWeightFactoryClass; return (Config_) this; } - public @NonNull Config_ withSelectedCountLimit(@NonNull Long selectedCountLimit) { + public Config_ withSelectedCountLimit(Long selectedCountLimit) { this.selectedCountLimit = selectedCountLimit; return (Config_) this; } - public @NonNull Config_ withFixedProbabilityWeight(@NonNull Double fixedProbabilityWeight) { + public Config_ withFixedProbabilityWeight(Double fixedProbabilityWeight) { this.fixedProbabilityWeight = fixedProbabilityWeight; return (Config_) this; } @@ -303,12 +316,12 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) { * except for {@link UnionMoveSelectorConfig} and {@link CartesianProductMoveSelectorConfig}. * */ - public void extractLeafMoveSelectorConfigsIntoList(@NonNull List<@NonNull MoveSelectorConfig> leafMoveSelectorConfigList) { + public void extractLeafMoveSelectorConfigsIntoList(List leafMoveSelectorConfigList) { leafMoveSelectorConfigList.add(this); } @Override - public @NonNull Config_ inherit(@NonNull Config_ inheritedConfig) { + public Config_ inherit(Config_ inheritedConfig) { inheritCommon(inheritedConfig); return (Config_) this; } @@ -316,11 +329,11 @@ public void extractLeafMoveSelectorConfigsIntoList(@NonNull List<@NonNull MoveSe /** * Does not inherit subclass properties because this class and {@code foldedConfig} can be of a different type. */ - public void inheritFolded(@NonNull MoveSelectorConfig foldedConfig) { + public void inheritFolded(MoveSelectorConfig foldedConfig) { inheritCommon(foldedConfig); } - protected void visitCommonReferencedClasses(@NonNull Consumer> classVisitor) { + protected void visitCommonReferencedClasses(Consumer> classVisitor) { classVisitor.accept(filterClass); classVisitor.accept(sorterComparatorClass); classVisitor.accept(comparatorClass); diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index e688753b44..705f6ee88b 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -20,7 +20,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @XmlType(propOrder = { @@ -42,49 +42,67 @@ "probabilityWeightFactoryClass", "selectedCountLimit" }) +@NullMarked public class ValueSelectorConfig extends SelectorConfig { @XmlAttribute + @Nullable protected String id = null; @XmlAttribute + @Nullable protected String mimicSelectorRef = null; + @Nullable protected Class downcastEntityClass = null; @XmlAttribute + @Nullable protected String variableName = null; + @Nullable protected SelectionCacheType cacheType = null; + @Nullable protected SelectionOrder selectionOrder = null; @XmlElement(name = "nearbySelection") + @Nullable protected NearbySelectionConfig nearbySelectionConfig = null; + @Nullable protected Class filterClass = null; + @Nullable protected ValueSorterManner sorterManner = null; /** * @deprecated Deprecated in favor of {@link #comparatorClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterComparatorClass = null; + @Nullable protected Class comparatorClass = null; /** * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}. */ @Deprecated(forRemoval = true, since = "1.28.0") + @Nullable protected Class sorterWeightFactoryClass = null; + @Nullable protected Class comparatorFactoryClass = null; + @Nullable protected SelectionSorterOrder sorterOrder = null; + @Nullable protected Class sorterClass = null; + @Nullable protected Class probabilityWeightFactoryClass = null; + @Nullable protected Long selectedCountLimit = null; public ValueSelectorConfig() { } - public ValueSelectorConfig(@NonNull String variableName) { + public ValueSelectorConfig(String variableName) { this.variableName = variableName; } @@ -182,11 +200,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public Class getComparatorClass() { + public @Nullable Class getComparatorClass() { return comparatorClass; } - public void setComparatorClass(Class comparatorClass) { + public void setComparatorClass(@Nullable Class comparatorClass) { this.comparatorClass = comparatorClass; } @@ -207,11 +225,11 @@ public void setSorterWeightFactoryClass(@Nullable Class getComparatorFactoryClass() { + public @Nullable Class getComparatorFactoryClass() { return comparatorFactoryClass; } - public void setComparatorFactoryClass(Class comparatorFactoryClass) { + public void setComparatorFactoryClass(@Nullable Class comparatorFactoryClass) { this.comparatorFactoryClass = comparatorFactoryClass; } @@ -252,47 +270,47 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { // With methods // ************************************************************************ - public @NonNull ValueSelectorConfig withId(@NonNull String id) { + public ValueSelectorConfig withId(String id) { this.setId(id); return this; } - public @NonNull ValueSelectorConfig withMimicSelectorRef(@NonNull String mimicSelectorRef) { + public ValueSelectorConfig withMimicSelectorRef(String mimicSelectorRef) { this.setMimicSelectorRef(mimicSelectorRef); return this; } - public @NonNull ValueSelectorConfig withDowncastEntityClass(@NonNull Class entityClass) { + public ValueSelectorConfig withDowncastEntityClass(Class entityClass) { this.setDowncastEntityClass(entityClass); return this; } - public @NonNull ValueSelectorConfig withVariableName(@NonNull String variableName) { + public ValueSelectorConfig withVariableName(String variableName) { this.setVariableName(variableName); return this; } - public @NonNull ValueSelectorConfig withCacheType(@NonNull SelectionCacheType cacheType) { + public ValueSelectorConfig withCacheType(SelectionCacheType cacheType) { this.setCacheType(cacheType); return this; } - public @NonNull ValueSelectorConfig withSelectionOrder(@NonNull SelectionOrder selectionOrder) { + public ValueSelectorConfig withSelectionOrder(SelectionOrder selectionOrder) { this.setSelectionOrder(selectionOrder); return this; } - public @NonNull ValueSelectorConfig withNearbySelectionConfig(@NonNull NearbySelectionConfig nearbySelectionConfig) { + public ValueSelectorConfig withNearbySelectionConfig(NearbySelectionConfig nearbySelectionConfig) { this.setNearbySelectionConfig(nearbySelectionConfig); return this; } - public @NonNull ValueSelectorConfig withFilterClass(@NonNull Class filterClass) { + public ValueSelectorConfig withFilterClass(Class filterClass) { this.setFilterClass(filterClass); return this; } - public @NonNull ValueSelectorConfig withSorterManner(@NonNull ValueSorterManner sorterManner) { + public ValueSelectorConfig withSorterManner(ValueSorterManner sorterManner) { this.setSorterManner(sorterManner); return this; } @@ -301,12 +319,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull ValueSelectorConfig withSorterComparatorClass(@NonNull Class comparatorClass) { + public ValueSelectorConfig withSorterComparatorClass(Class comparatorClass) { this.setSorterComparatorClass(comparatorClass); return this; } - public @NonNull ValueSelectorConfig withComparatorClass(@NonNull Class comparatorClass) { + public ValueSelectorConfig withComparatorClass(Class comparatorClass) { this.setComparatorClass(comparatorClass); return this; } @@ -316,35 +334,35 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { * @param weightFactoryClass the factory class */ @Deprecated(forRemoval = true, since = "1.28.0") - public @NonNull ValueSelectorConfig - withSorterWeightFactoryClass(@NonNull Class weightFactoryClass) { + public ValueSelectorConfig + withSorterWeightFactoryClass(Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } - public @NonNull ValueSelectorConfig - withComparatorFactoryClass(@NonNull Class comparatorFactoryClass) { + public ValueSelectorConfig + withComparatorFactoryClass(Class comparatorFactoryClass) { this.setComparatorFactoryClass(comparatorFactoryClass); return this; } - public @NonNull ValueSelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) { + public ValueSelectorConfig withSorterOrder(SelectionSorterOrder sorterOrder) { this.setSorterOrder(sorterOrder); return this; } - public @NonNull ValueSelectorConfig withSorterClass(@NonNull Class sorterClass) { + public ValueSelectorConfig withSorterClass(Class sorterClass) { this.setSorterClass(sorterClass); return this; } - public @NonNull ValueSelectorConfig - withProbabilityWeightFactoryClass(@NonNull Class factoryClass) { + public ValueSelectorConfig + withProbabilityWeightFactoryClass(Class factoryClass) { this.setProbabilityWeightFactoryClass(factoryClass); return this; } - public @NonNull ValueSelectorConfig withSelectedCountLimit(long selectedCountLimit) { + public ValueSelectorConfig withSelectedCountLimit(long selectedCountLimit) { this.setSelectedCountLimit(selectedCountLimit); return this; } @@ -354,7 +372,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { // ************************************************************************ @Override - public @NonNull ValueSelectorConfig inherit(@NonNull ValueSelectorConfig inheritedConfig) { + public ValueSelectorConfig inherit(ValueSelectorConfig inheritedConfig) { id = ConfigUtils.inheritOverwritableProperty(id, inheritedConfig.getId()); mimicSelectorRef = ConfigUtils.inheritOverwritableProperty(mimicSelectorRef, inheritedConfig.getMimicSelectorRef()); @@ -387,12 +405,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) { } @Override - public @NonNull ValueSelectorConfig copyConfig() { + public ValueSelectorConfig copyConfig() { return new ValueSelectorConfig().inherit(this); } @Override - public void visitReferencedClasses(@NonNull Consumer> classVisitor) { + public void visitReferencedClasses(Consumer> classVisitor) { classVisitor.accept(downcastEntityClass); if (nearbySelectionConfig != null) { nearbySelectionConfig.visitReferencedClasses(classVisitor); @@ -411,8 +429,8 @@ public String toString() { return getClass().getSimpleName() + "(" + variableName + ")"; } - public static boolean hasSorter(@NonNull ValueSorterManner valueSorterManner, - @NonNull GenuineVariableDescriptor variableDescriptor) { + public static boolean hasSorter(ValueSorterManner valueSorterManner, + GenuineVariableDescriptor variableDescriptor) { return switch (valueSorterManner) { case NONE -> false; case INCREASING_STRENGTH, DECREASING_STRENGTH, ASCENDING, DESCENDING -> true; @@ -423,8 +441,8 @@ public static boolean hasSorter(@NonNull ValueSorterManner valueSort }; } - public static @NonNull SelectionSorter determineSorter( - @NonNull ValueSorterManner valueSorterManner, @NonNull GenuineVariableDescriptor variableDescriptor) { + public static SelectionSorter determineSorter( + ValueSorterManner valueSorterManner, GenuineVariableDescriptor variableDescriptor) { SelectionSorter sorter = switch (valueSorterManner) { case NONE -> throw new IllegalStateException("Impossible state: hasSorter() should have returned null."); case INCREASING_STRENGTH, INCREASING_STRENGTH_IF_AVAILABLE, ASCENDING, ASCENDING_IF_AVAILABLE -> diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index 15215b4b48..29f8eecf81 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -67,12 +67,10 @@ public void sort(Solution_ solution, List selectionList) { } @Override - public boolean equals(Object other) { - if (this == other) - return true; - if (other == null || getClass() != other.getClass()) + public boolean equals(Object o) { + if (!(o instanceof FactorySelectionSorter that)) { return false; - var that = (FactorySelectionSorter) other; + } return Objects.equals(selectionComparatorFactory, that.selectionComparatorFactory) && Objects.equals(appliedComparator, that.appliedComparator); } From b18c9e4d87c804d374ed123f167ffd75dc0f818e Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 10:12:59 -0300 Subject: [PATCH 27/33] chore: address comments --- .../ConstructionHeuristicType.java | 2 +- ...DefaultConstructionHeuristicPhaseTest.java | 105 +++++++++--------- ...rEntityComparatorEasyScoreCalculator.java} | 12 +- ... => TestdataComparatorSortableEntity.java} | 8 +- .../TestdataComparatorSortableSolution.java} | 24 ++-- ...rEntityDifficultyEasyScoreCalculator.java} | 12 +- .../TestdataDifficultySortableEntity.java} | 8 +- .../TestdataDifficultySortableSolution.java} | 24 ++-- ...ePerEntityFactoryEasyScoreCalculator.java} | 12 +- ...ava => TestdataFactorySortableEntity.java} | 8 +- .../TestdataFactorySortableSolution.java} | 24 ++-- ...DifficultyFactoryEasyScoreCalculator.java} | 12 +- ...tdataDifficultyFactorySortableEntity.java} | 8 +- ...ataDifficultyFactorySortableSolution.java} | 24 ++-- ...tyComparatorRangeEasyScoreCalculator.java} | 12 +- ...paratorSortableEntityProvidingEntity.java} | 8 +- ...ratorSortableEntityProvidingSolution.java} | 24 ++-- ...tityStrengthRangeEasyScoreCalculator.java} | 12 +- ...trengthSortableEntityProvidingEntity.java} | 8 +- ...engthSortableEntityProvidingSolution.java} | 24 ++-- ...ntityFactoryRangeEasyScoreCalculator.java} | 12 +- ...FactorySortableEntityProvidingEntity.java} | 8 +- ...ctorySortableEntityProvidingSolution.java} | 24 ++-- ...engthFactoryRangeEasyScoreCalculator.java} | 12 +- ...FactorySortableEntityProvidingEntity.java} | 8 +- ...ctorySortableEntityProvidingSolution.java} | 24 ++-- 26 files changed, 230 insertions(+), 229 deletions(-) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/{newapproach/NewOneValuePerEntityEasyScoreCalculator.java => OneValuePerEntityComparatorEasyScoreCalculator.java} (64%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/{newapproach/TestdataNewSortableEntity.java => TestdataComparatorSortableEntity.java} (77%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{factory/newapproach/TestdataFactoryNewSortableSolution.java => comparator/TestdataComparatorSortableSolution.java} (75%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java => comparatordifficulty/OneValuePerEntityDifficultyEasyScoreCalculator.java} (63%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{comparator/oldapproach/TestdataOldSortableEntity.java => comparatordifficulty/TestdataDifficultySortableEntity.java} (77%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{factory/oldapproach/TestdataFactoryOldSortableSolution.java => comparatordifficulty/TestdataDifficultySortableSolution.java} (75%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/{oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java => OneValuePerEntityFactoryEasyScoreCalculator.java} (60%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/{newapproach/TestdataFactoryNewSortableEntity.java => TestdataFactorySortableEntity.java} (77%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{comparator/oldapproach/TestdataOldSortableSolution.java => factory/TestdataFactorySortableSolution.java} (71%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java => factorydifficulty/OneValuePerEntityDifficultyFactoryEasyScoreCalculator.java} (58%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{factory/oldapproach/TestdataFactoryOldSortableEntity.java => factorydifficulty/TestdataDifficultyFactorySortableEntity.java} (76%) rename core/src/test/java/ai/timefold/solver/core/testdomain/sort/{comparator/newapproach/TestdataNewSortableSolution.java => factorydifficulty/TestdataDifficultyFactorySortableSolution.java} (68%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/{oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java => OneValuePerEntityComparatorRangeEasyScoreCalculator.java} (62%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/{newapproach/TestdataNewSortableEntityProvidingEntity.java => TestdataComparatorSortableEntityProvidingEntity.java} (85%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java => comparator/TestdataComparatorSortableEntityProvidingSolution.java} (73%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java => comparatorstrength/OneValuePerEntityStrengthRangeEasyScoreCalculator.java} (62%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java => comparatorstrength/TestdataStrengthSortableEntityProvidingEntity.java} (85%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java => comparatorstrength/TestdataStrengthSortableEntityProvidingSolution.java} (66%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/{newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java => OneValuePerEntityFactoryRangeEasyScoreCalculator.java} (63%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/{newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java => TestdataFactorySortableEntityProvidingEntity.java} (86%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/{oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java => TestdataFactorySortableEntityProvidingSolution.java} (65%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java => factorystrength/OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator.java} (62%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java => factorystrength/TestdataStrengthFactorySortableEntityProvidingEntity.java} (85%) rename core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/{comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java => factorystrength/TestdataStrengthFactorySortableEntityProvidingSolution.java} (62%) diff --git a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java index aa43642ff8..cda2ed6781 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java +++ b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java @@ -80,7 +80,7 @@ public ValueSorterManner getDefaultValueSorterManner() { * @return {@link ConstructionHeuristicType#values()} without duplicates (abstract types that end up behaving as one of the * other types). */ - public static ConstructionHeuristicType [] getBluePrintTypes() { + public static ConstructionHeuristicType[] getBluePrintTypes() { return new ConstructionHeuristicType[] { FIRST_FIT, FIRST_FIT_DECREASING, 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 b9a8538595..6a99a979d6 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 @@ -68,18 +68,18 @@ import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity; import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedSolution; -import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.NewOneValuePerEntityEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableEntity; -import ai.timefold.solver.core.testdomain.sort.comparator.newapproach.TestdataNewSortableSolution; -import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.OldOneValuePerEntityEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.TestdataOldSortableEntity; -import ai.timefold.solver.core.testdomain.sort.comparator.oldapproach.TestdataOldSortableSolution; -import ai.timefold.solver.core.testdomain.sort.factory.newapproach.NewOneValuePerEntityFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.factory.newapproach.TestdataFactoryNewSortableEntity; -import ai.timefold.solver.core.testdomain.sort.factory.newapproach.TestdataFactoryNewSortableSolution; -import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.OldOneValuePerEntityFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.TestdataFactoryOldSortableEntity; -import ai.timefold.solver.core.testdomain.sort.factory.oldapproach.TestdataFactoryOldSortableSolution; +import ai.timefold.solver.core.testdomain.sort.comparator.OneValuePerEntityComparatorEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableSolution; +import ai.timefold.solver.core.testdomain.sort.comparatordifficulty.OneValuePerEntityDifficultyEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.comparatordifficulty.TestdataDifficultySortableEntity; +import ai.timefold.solver.core.testdomain.sort.comparatordifficulty.TestdataDifficultySortableSolution; +import ai.timefold.solver.core.testdomain.sort.factory.OneValuePerEntityFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.factory.TestdataFactorySortableSolution; +import ai.timefold.solver.core.testdomain.sort.factorydifficulty.OneValuePerEntityDifficultyFactoryEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.sort.factorydifficulty.TestdataDifficultyFactorySortableEntity; +import ai.timefold.solver.core.testdomain.sort.factorydifficulty.TestdataDifficultyFactorySortableSolution; import ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableEntity; import ai.timefold.solver.core.testdomain.sort.invalid.mixed.comparator.TestdataInvalidMixedComparatorSortableSolution; import ai.timefold.solver.core.testdomain.sort.invalid.mixed.strength.TestdataInvalidMixedStrengthSortableEntity; @@ -98,18 +98,18 @@ import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingEntity; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingScoreCalculator; import ai.timefold.solver.core.testdomain.valuerange.entityproviding.unassignedvar.TestdataAllowsUnassignedEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.NewOneValuePerEntityRangeEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.TestdataNewSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach.TestdataNewSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.OldOneValuePerEntityRangeEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.TestdataOldSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach.TestdataOldSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.NewOneValuePerEntityRangeFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.TestdataFactoryNewSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach.TestdataFactoryNewSortableEntityProvidingSolution; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.OldOneValuePerEntityRangeFactoryEasyScoreCalculator; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.TestdataFactoryOldSortableEntityProvidingEntity; -import ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach.TestdataFactoryOldSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.OneValuePerEntityComparatorRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataComparatorSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparator.TestdataComparatorSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength.OneValuePerEntityStrengthRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength.TestdataStrengthSortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength.TestdataStrengthSortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.OneValuePerEntityFactoryRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.factory.TestdataFactorySortableEntityProvidingSolution; +import ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength.OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength.TestdataStrengthFactorySortableEntityProvidingEntity; +import ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength.TestdataStrengthFactorySortableEntityProvidingSolution; import ai.timefold.solver.core.testutil.PlannerTestUtils; import org.jspecify.annotations.NonNull; @@ -705,11 +705,11 @@ private static List generateBasicVariableConfig @MethodSource("generateBasicVariableConfiguration") void solveOldBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataOldSortableSolution.class, TestdataOldSortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(OldOneValuePerEntityEasyScoreCalculator.class); + .buildSolverConfig(TestdataDifficultySortableSolution.class, TestdataDifficultySortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityDifficultyEasyScoreCalculator.class); solverConfig.withPhases(phaseConfig.config()); - var solution = TestdataOldSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataDifficultySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -730,11 +730,12 @@ void solveOldBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseC @MethodSource("generateBasicVariableConfiguration") void solveOldBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataFactoryOldSortableSolution.class, TestdataFactoryOldSortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(OldOneValuePerEntityFactoryEasyScoreCalculator.class); + .buildSolverConfig(TestdataDifficultyFactorySortableSolution.class, + TestdataDifficultyFactorySortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityDifficultyFactoryEasyScoreCalculator.class); solverConfig.withPhases(phaseConfig.config()); - var solution = TestdataFactoryOldSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataDifficultyFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -756,12 +757,12 @@ void solveOldBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConf void solveOldBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataOldSortableEntityProvidingSolution.class, - TestdataOldSortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(OldOneValuePerEntityRangeEasyScoreCalculator.class) + .buildSolverConfig(TestdataStrengthSortableEntityProvidingSolution.class, + TestdataStrengthSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityStrengthRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataOldSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataStrengthSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -783,12 +784,12 @@ void solveOldBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestCo void solveOldBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataFactoryOldSortableEntityProvidingSolution.class, - TestdataFactoryOldSortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(OldOneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .buildSolverConfig(TestdataStrengthFactorySortableEntityProvidingSolution.class, + TestdataStrengthFactorySortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataFactoryOldSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataStrengthFactorySortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -809,11 +810,11 @@ void solveOldBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi @MethodSource("generateBasicVariableConfiguration") void solveNewBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataNewSortableSolution.class, TestdataNewSortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(NewOneValuePerEntityEasyScoreCalculator.class); + .buildSolverConfig(TestdataComparatorSortableSolution.class, TestdataComparatorSortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityComparatorEasyScoreCalculator.class); solverConfig.withPhases(phaseConfig.config()); - var solution = TestdataNewSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataComparatorSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -834,11 +835,11 @@ void solveNewBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseC @MethodSource("generateBasicVariableConfiguration") void solveNewBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataFactoryNewSortableSolution.class, TestdataFactoryNewSortableEntity.class); - solverConfig.withEasyScoreCalculatorClass(NewOneValuePerEntityFactoryEasyScoreCalculator.class); + .buildSolverConfig(TestdataFactorySortableSolution.class, TestdataFactorySortableEntity.class); + solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class); solverConfig.withPhases(phaseConfig.config()); - var solution = TestdataFactoryNewSortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataFactorySortableSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -860,12 +861,12 @@ void solveNewBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConf void solveNewBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataNewSortableEntityProvidingSolution.class, - TestdataNewSortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(NewOneValuePerEntityRangeEasyScoreCalculator.class) + .buildSolverConfig(TestdataComparatorSortableEntityProvidingSolution.class, + TestdataComparatorSortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityComparatorRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataNewSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataComparatorSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); @@ -887,12 +888,12 @@ void solveNewBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestCo void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils - .buildSolverConfig(TestdataFactoryNewSortableEntityProvidingSolution.class, - TestdataFactoryNewSortableEntityProvidingEntity.class) - .withEasyScoreCalculatorClass(NewOneValuePerEntityRangeFactoryEasyScoreCalculator.class) + .buildSolverConfig(TestdataFactorySortableEntityProvidingSolution.class, + TestdataFactorySortableEntityProvidingEntity.class) + .withEasyScoreCalculatorClass(OneValuePerEntityFactoryRangeEasyScoreCalculator.class) .withPhases(phaseConfig.config()); - var solution = TestdataFactoryNewSortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); + var solution = TestdataFactorySortableEntityProvidingSolution.generateSolution(3, 3, phaseConfig.shuffle()); solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityComparatorEasyScoreCalculator.java similarity index 64% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityComparatorEasyScoreCalculator.java index 8f4905138a..41b83def85 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/NewOneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/OneValuePerEntityComparatorEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.sort.comparator; import java.util.Objects; @@ -7,18 +7,18 @@ import org.jspecify.annotations.NonNull; -public class NewOneValuePerEntityEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityComparatorEasyScoreCalculator + implements EasyScoreCalculator { @Override - public @NonNull HardSoftScore calculateScore(@NonNull TestdataNewSortableSolution solution) { + public @NonNull HardSoftScore calculateScore(@NonNull TestdataComparatorSortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataNewSortableEntity::getValue) + .map(TestdataComparatorSortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataNewSortableEntity::getValue) + .map(TestdataComparatorSortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java similarity index 77% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java index a1120e2dbd..7acebfe425 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.sort.comparator; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -8,16 +8,16 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(comparatorClass = TestSortableComparator.class) -public class TestdataNewSortableEntity extends TestdataObject implements TestSortableObject { +public class TestdataComparatorSortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) private TestdataSortableValue value; private int difficulty; - public TestdataNewSortableEntity() { + public TestdataComparatorSortableEntity() { } - public TestdataNewSortableEntity(String code, int difficulty) { + public TestdataComparatorSortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableSolution.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableSolution.java index 5d41258706..f76c016a50 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.sort.comparator; import java.util.ArrayList; import java.util.Collections; @@ -16,18 +16,18 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactoryNewSortableSolution { +public class TestdataComparatorSortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactoryNewSortableSolution.class, - TestdataFactoryNewSortableEntity.class, + TestdataComparatorSortableSolution.class, + TestdataComparatorSortableEntity.class, TestdataSortableValue.class); } - public static TestdataFactoryNewSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataComparatorSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactoryNewSortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataComparatorSortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) @@ -37,14 +37,14 @@ public static TestdataFactoryNewSortableSolution generateSolution(int valueCount Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataFactoryNewSortableSolution solution = new TestdataFactoryNewSortableSolution(); + TestdataComparatorSortableSolution solution = new TestdataComparatorSortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -58,11 +58,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -75,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactoryNewSortableEntity entity) { + public void removeEntity(TestdataComparatorSortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/OneValuePerEntityDifficultyEasyScoreCalculator.java similarity index 63% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/OneValuePerEntityDifficultyEasyScoreCalculator.java index ea74449af5..0bfe22b776 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/OldOneValuePerEntityEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/OneValuePerEntityDifficultyEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.sort.comparatordifficulty; import java.util.Objects; @@ -7,18 +7,18 @@ import org.jspecify.annotations.NonNull; -public class OldOneValuePerEntityEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityDifficultyEasyScoreCalculator + implements EasyScoreCalculator { @Override - public @NonNull HardSoftScore calculateScore(@NonNull TestdataOldSortableSolution solution) { + public @NonNull HardSoftScore calculateScore(@NonNull TestdataDifficultySortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataOldSortableEntity::getValue) + .map(TestdataDifficultySortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataOldSortableEntity::getValue) + .map(TestdataDifficultySortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableEntity.java similarity index 77% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableEntity.java index bf677dbb48..3c8ea7ac36 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.sort.comparatordifficulty; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -8,16 +8,16 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) -public class TestdataOldSortableEntity extends TestdataObject implements TestSortableObject { +public class TestdataDifficultySortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) private TestdataSortableValue value; private int difficulty; - public TestdataOldSortableEntity() { + public TestdataDifficultySortableEntity() { } - public TestdataOldSortableEntity(String code, int difficulty) { + public TestdataDifficultySortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableSolution.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableSolution.java index 7891bb0dd0..643879c4e1 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparatordifficulty/TestdataDifficultySortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.sort.comparatordifficulty; import java.util.ArrayList; import java.util.Collections; @@ -16,18 +16,18 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactoryOldSortableSolution { +public class TestdataDifficultySortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactoryOldSortableSolution.class, - TestdataFactoryOldSortableEntity.class, + TestdataDifficultySortableSolution.class, + TestdataDifficultySortableEntity.class, TestdataSortableValue.class); } - public static TestdataFactoryOldSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataDifficultySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactoryOldSortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataDifficultySortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) @@ -37,14 +37,14 @@ public static TestdataFactoryOldSortableSolution generateSolution(int valueCount Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataFactoryOldSortableSolution solution = new TestdataFactoryOldSortableSolution(); + TestdataDifficultySortableSolution solution = new TestdataDifficultySortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -58,11 +58,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -75,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactoryOldSortableEntity entity) { + public void removeEntity(TestdataDifficultySortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java similarity index 60% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java index f2fb3b4af9..19d08a52e9 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/OldOneValuePerEntityFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/OneValuePerEntityFactoryEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.sort.factory; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class OldOneValuePerEntityFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityFactoryEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataFactoryOldSortableSolution solution) { + calculateScore(@NonNull TestdataFactorySortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactoryOldSortableEntity::getValue) + .map(TestdataFactorySortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactoryOldSortableEntity::getValue) + .map(TestdataFactorySortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java similarity index 77% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java index 47caf8a3a5..f38c8f15c4 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/TestdataFactoryNewSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.sort.factory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -8,16 +8,16 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) -public class TestdataFactoryNewSortableEntity extends TestdataObject implements TestSortableObject { +public class TestdataFactorySortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) private TestdataSortableValue value; private int difficulty; - public TestdataFactoryNewSortableEntity() { + public TestdataFactorySortableEntity() { } - public TestdataFactoryNewSortableEntity(String code, int difficulty) { + public TestdataFactorySortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java similarity index 71% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java index b60c017688..c775c9a308 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/oldapproach/TestdataOldSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.sort.factory; import java.util.ArrayList; import java.util.Collections; @@ -16,18 +16,18 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataOldSortableSolution { +public class TestdataFactorySortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataOldSortableSolution.class, - TestdataOldSortableEntity.class, + TestdataFactorySortableSolution.class, + TestdataFactorySortableEntity.class, TestdataSortableValue.class); } - public static TestdataOldSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataOldSortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataFactorySortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) @@ -37,14 +37,14 @@ public static TestdataOldSortableSolution generateSolution(int valueCount, int e Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataOldSortableSolution solution = new TestdataOldSortableSolution(); + TestdataFactorySortableSolution solution = new TestdataFactorySortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -58,11 +58,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -75,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataOldSortableEntity entity) { + public void removeEntity(TestdataFactorySortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/OneValuePerEntityDifficultyFactoryEasyScoreCalculator.java similarity index 58% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/OneValuePerEntityDifficultyFactoryEasyScoreCalculator.java index 0de749ccf3..14a79fe330 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/newapproach/NewOneValuePerEntityFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/OneValuePerEntityDifficultyFactoryEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.sort.factorydifficulty; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class NewOneValuePerEntityFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityDifficultyFactoryEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataFactoryNewSortableSolution solution) { + calculateScore(@NonNull TestdataDifficultyFactorySortableSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactoryNewSortableEntity::getValue) + .map(TestdataDifficultyFactorySortableEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactoryNewSortableEntity::getValue) + .map(TestdataDifficultyFactorySortableEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableEntity.java similarity index 76% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableEntity.java index dfe74589a0..c45b154a89 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/oldapproach/TestdataFactoryOldSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.sort.factorydifficulty; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -8,16 +8,16 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) -public class TestdataFactoryOldSortableEntity extends TestdataObject implements TestSortableObject { +public class TestdataDifficultyFactorySortableEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = TestSortableFactory.class) private TestdataSortableValue value; private int difficulty; - public TestdataFactoryOldSortableEntity() { + public TestdataDifficultyFactorySortableEntity() { } - public TestdataFactoryOldSortableEntity(String code, int difficulty) { + public TestdataDifficultyFactorySortableEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableSolution.java similarity index 68% rename from core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableSolution.java index 6d1d379629..a1a6b75668 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/newapproach/TestdataNewSortableSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factorydifficulty/TestdataDifficultyFactorySortableSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.sort.factorydifficulty; import java.util.ArrayList; import java.util.Collections; @@ -16,18 +16,18 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataNewSortableSolution { +public class TestdataDifficultyFactorySortableSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataNewSortableSolution.class, - TestdataNewSortableEntity.class, + TestdataDifficultyFactorySortableSolution.class, + TestdataDifficultyFactorySortableEntity.class, TestdataSortableValue.class); } - public static TestdataNewSortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { + public static TestdataDifficultyFactorySortableSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataNewSortableEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataDifficultyFactorySortableEntity("Generated Entity " + i, i)) .toList()); var valueList = new ArrayList<>(IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) @@ -37,14 +37,14 @@ public static TestdataNewSortableSolution generateSolution(int valueCount, int e Collections.shuffle(entityList, random); Collections.shuffle(valueList, random); } - TestdataNewSortableSolution solution = new TestdataNewSortableSolution(); + TestdataDifficultyFactorySortableSolution solution = new TestdataDifficultyFactorySortableSolution(); solution.setValueList(valueList); solution.setEntityList(entityList); return solution; } private List valueList; - private List entityList; + private List entityList; private HardSoftScore score; @ValueRangeProvider(id = "valueRange") @@ -58,11 +58,11 @@ public void setValueList(List valueList) { } @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -75,7 +75,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataNewSortableEntity entity) { + public void removeEntity(TestdataDifficultyFactorySortableEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityComparatorRangeEasyScoreCalculator.java similarity index 62% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityComparatorRangeEasyScoreCalculator.java index bc17befbc1..5af384b43f 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/OldOneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/OneValuePerEntityComparatorRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class OldOneValuePerEntityRangeEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityComparatorRangeEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataOldSortableEntityProvidingSolution solution) { + calculateScore(@NonNull TestdataComparatorSortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataOldSortableEntityProvidingEntity::getValue) + .map(TestdataComparatorSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataOldSortableEntityProvidingEntity::getValue) + .map(TestdataComparatorSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java similarity index 85% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java index bd5041e9f5..8fc6b593bd 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; import java.util.List; @@ -12,7 +12,7 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) -public class TestdataNewSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { +public class TestdataComparatorSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) private TestdataSortableValue value; @@ -22,10 +22,10 @@ public class TestdataNewSortableEntityProvidingEntity extends TestdataObject imp private int difficulty; - public TestdataNewSortableEntityProvidingEntity() { + public TestdataComparatorSortableEntityProvidingEntity() { } - public TestdataNewSortableEntityProvidingEntity(String code, int difficulty) { + public TestdataComparatorSortableEntityProvidingEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingSolution.java similarity index 73% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingSolution.java index 2702345a2e..0a108b6d9a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparator; import java.util.ArrayList; import java.util.Collections; @@ -14,24 +14,24 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactoryNewSortableEntityProvidingSolution { +public class TestdataComparatorSortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactoryNewSortableEntityProvidingSolution.class, - TestdataFactoryNewSortableEntityProvidingEntity.class); + TestdataComparatorSortableEntityProvidingSolution.class, + TestdataComparatorSortableEntityProvidingEntity.class); } - public static TestdataFactoryNewSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + public static TestdataComparatorSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactoryNewSortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataComparatorSortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); - var solution = new TestdataFactoryNewSortableEntityProvidingSolution(); var random = new Random(0); + var solution = new TestdataComparatorSortableEntityProvidingSolution(); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); if (shuffle) { @@ -46,15 +46,15 @@ public static TestdataFactoryNewSortableEntityProvidingSolution generateSolution return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactoryNewSortableEntityProvidingEntity entity) { + public void removeEntity(TestdataComparatorSortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/OneValuePerEntityStrengthRangeEasyScoreCalculator.java similarity index 62% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/OneValuePerEntityStrengthRangeEasyScoreCalculator.java index 340b254c14..37ff33cf77 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/NewOneValuePerEntityRangeEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/OneValuePerEntityStrengthRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength; import java.util.Objects; @@ -7,19 +7,19 @@ import org.jspecify.annotations.NonNull; -public class NewOneValuePerEntityRangeEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityStrengthRangeEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore - calculateScore(@NonNull TestdataNewSortableEntityProvidingSolution solution) { + calculateScore(@NonNull TestdataStrengthSortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataNewSortableEntityProvidingEntity::getValue) + .map(TestdataStrengthSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataNewSortableEntityProvidingEntity::getValue) + .map(TestdataStrengthSortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingEntity.java similarity index 85% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingEntity.java index 852aba2216..d9a8a4b30a 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength; import java.util.List; @@ -12,7 +12,7 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyComparatorClass = TestSortableComparator.class) -public class TestdataOldSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { +public class TestdataStrengthSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthComparatorClass = TestSortableComparator.class) private TestdataSortableValue value; @@ -22,10 +22,10 @@ public class TestdataOldSortableEntityProvidingEntity extends TestdataObject imp private int difficulty; - public TestdataOldSortableEntityProvidingEntity() { + public TestdataStrengthSortableEntityProvidingEntity() { } - public TestdataOldSortableEntityProvidingEntity(String code, int difficulty) { + public TestdataStrengthSortableEntityProvidingEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingSolution.java similarity index 66% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingSolution.java index 5b46cfcaf3..e2d6537c83 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/newapproach/TestdataNewSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparatorstrength/TestdataStrengthSortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.comparatorstrength; import java.util.ArrayList; import java.util.Collections; @@ -14,24 +14,24 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataNewSortableEntityProvidingSolution { +public class TestdataStrengthSortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataNewSortableEntityProvidingSolution.class, - TestdataNewSortableEntityProvidingEntity.class); + TestdataStrengthSortableEntityProvidingSolution.class, + TestdataStrengthSortableEntityProvidingEntity.class); } - public static TestdataNewSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + public static TestdataStrengthSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataNewSortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataStrengthSortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); var random = new Random(0); - var solution = new TestdataNewSortableEntityProvidingSolution(); + var solution = new TestdataStrengthSortableEntityProvidingSolution(); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); if (shuffle) { @@ -46,15 +46,15 @@ public static TestdataNewSortableEntityProvidingSolution generateSolution(int va return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataNewSortableEntityProvidingEntity entity) { + public void removeEntity(TestdataStrengthSortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityFactoryRangeEasyScoreCalculator.java similarity index 63% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityFactoryRangeEasyScoreCalculator.java index d5fbb3d332..dd4a09a01f 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/NewOneValuePerEntityRangeFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/OneValuePerEntityFactoryRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; import java.util.Objects; @@ -7,20 +7,20 @@ import org.jspecify.annotations.NonNull; -public class NewOneValuePerEntityRangeFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityFactoryRangeEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore calculateScore( - @NonNull TestdataFactoryNewSortableEntityProvidingSolution solution) { + @NonNull TestdataFactorySortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactoryNewSortableEntityProvidingEntity::getValue) + .map(TestdataFactorySortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactoryNewSortableEntityProvidingEntity::getValue) + .map(TestdataFactorySortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java similarity index 86% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java index d8a9d0310c..9e59a830d9 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/newapproach/TestdataFactoryNewSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.newapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; import java.util.List; @@ -12,7 +12,7 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) -public class TestdataFactoryNewSortableEntityProvidingEntity extends TestdataObject +public class TestdataFactorySortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) @@ -23,10 +23,10 @@ public class TestdataFactoryNewSortableEntityProvidingEntity extends TestdataObj private int difficulty; - public TestdataFactoryNewSortableEntityProvidingEntity() { + public TestdataFactorySortableEntityProvidingEntity() { } - public TestdataFactoryNewSortableEntityProvidingEntity(String code, int difficulty) { + public TestdataFactorySortableEntityProvidingEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java similarity index 65% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java index d60bb5dec9..e6f04f6300 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factory; import java.util.ArrayList; import java.util.Collections; @@ -14,23 +14,23 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataFactoryOldSortableEntityProvidingSolution { +public class TestdataFactorySortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataFactoryOldSortableEntityProvidingSolution.class, - TestdataFactoryOldSortableEntityProvidingEntity.class); + TestdataFactorySortableEntityProvidingSolution.class, + TestdataFactorySortableEntityProvidingEntity.class); } - public static TestdataFactoryOldSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + public static TestdataFactorySortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataFactoryOldSortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); - var solution = new TestdataFactoryOldSortableEntityProvidingSolution(); + var solution = new TestdataFactorySortableEntityProvidingSolution(); var random = new Random(0); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); @@ -46,15 +46,15 @@ public static TestdataFactoryOldSortableEntityProvidingSolution generateSolution return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataFactoryOldSortableEntityProvidingEntity entity) { + public void removeEntity(TestdataFactorySortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator.java similarity index 62% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator.java index 05e09ae1be..ab7e172c11 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/OldOneValuePerEntityRangeFactoryEasyScoreCalculator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength; import java.util.Objects; @@ -7,20 +7,20 @@ import org.jspecify.annotations.NonNull; -public class OldOneValuePerEntityRangeFactoryEasyScoreCalculator - implements EasyScoreCalculator { +public class OneValuePerEntityStrengthFactoryRangeEasyScoreCalculator + implements EasyScoreCalculator { @Override public @NonNull HardSoftScore calculateScore( - @NonNull TestdataFactoryOldSortableEntityProvidingSolution solution) { + @NonNull TestdataStrengthFactorySortableEntityProvidingSolution solution) { var distinct = (int) solution.getEntityList().stream() - .map(TestdataFactoryOldSortableEntityProvidingEntity::getValue) + .map(TestdataStrengthFactorySortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .distinct() .count(); var assigned = solution.getEntityList().stream() - .map(TestdataFactoryOldSortableEntityProvidingEntity::getValue) + .map(TestdataStrengthFactorySortableEntityProvidingEntity::getValue) .filter(Objects::nonNull) .count(); var repeated = (int) (assigned - distinct); diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingEntity.java similarity index 85% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingEntity.java index e156180e05..2c342cae16 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/oldapproach/TestdataFactoryOldSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingEntity.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.factory.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength; import java.util.List; @@ -12,7 +12,7 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity(difficultyWeightFactoryClass = TestSortableFactory.class) -public class TestdataFactoryOldSortableEntityProvidingEntity extends TestdataObject +public class TestdataStrengthFactorySortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", strengthWeightFactoryClass = TestSortableFactory.class) @@ -23,10 +23,10 @@ public class TestdataFactoryOldSortableEntityProvidingEntity extends TestdataObj private int difficulty; - public TestdataFactoryOldSortableEntityProvidingEntity() { + public TestdataStrengthFactorySortableEntityProvidingEntity() { } - public TestdataFactoryOldSortableEntityProvidingEntity(String code, int difficulty) { + public TestdataStrengthFactorySortableEntityProvidingEntity(String code, int difficulty) { super(code); this.difficulty = difficulty; } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingSolution.java similarity index 62% rename from core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingSolution.java index 4c8749c5c3..75d9f2b883 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/oldapproach/TestdataOldSortableEntityProvidingSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factorystrength/TestdataStrengthFactorySortableEntityProvidingSolution.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.testdomain.valuerange.sort.comparator.oldapproach; +package ai.timefold.solver.core.testdomain.valuerange.sort.factorystrength; import java.util.ArrayList; import java.util.Collections; @@ -14,24 +14,24 @@ import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningSolution -public class TestdataOldSortableEntityProvidingSolution { +public class TestdataStrengthFactorySortableEntityProvidingSolution { - public static SolutionDescriptor buildSolutionDescriptor() { + public static SolutionDescriptor buildSolutionDescriptor() { return SolutionDescriptor.buildSolutionDescriptor( - TestdataOldSortableEntityProvidingSolution.class, - TestdataOldSortableEntityProvidingEntity.class); + TestdataStrengthFactorySortableEntityProvidingSolution.class, + TestdataStrengthFactorySortableEntityProvidingEntity.class); } - public static TestdataOldSortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, + public static TestdataStrengthFactorySortableEntityProvidingSolution generateSolution(int valueCount, int entityCount, boolean shuffle) { var entityList = new ArrayList<>(IntStream.range(0, entityCount) - .mapToObj(i -> new TestdataOldSortableEntityProvidingEntity("Generated Entity " + i, i)) + .mapToObj(i -> new TestdataStrengthFactorySortableEntityProvidingEntity("Generated Entity " + i, i)) .toList()); var valueList = IntStream.range(0, valueCount) .mapToObj(i -> new TestdataSortableValue("Generated Value " + i, i)) .toList(); + var solution = new TestdataStrengthFactorySortableEntityProvidingSolution(); var random = new Random(0); - var solution = new TestdataOldSortableEntityProvidingSolution(); for (var entity : entityList) { var valueRange = new ArrayList<>(valueList); if (shuffle) { @@ -46,15 +46,15 @@ public static TestdataOldSortableEntityProvidingSolution generateSolution(int va return solution; } - private List entityList; + private List entityList; private HardSoftScore score; @PlanningEntityCollectionProperty - public List getEntityList() { + public List getEntityList() { return entityList; } - public void setEntityList(List entityList) { + public void setEntityList(List entityList) { this.entityList = entityList; } @@ -67,7 +67,7 @@ public void setScore(HardSoftScore score) { this.score = score; } - public void removeEntity(TestdataOldSortableEntityProvidingEntity entity) { + public void removeEntity(TestdataStrengthFactorySortableEntityProvidingEntity entity) { this.entityList = entityList.stream() .filter(e -> e != entity) .toList(); From 0c65111d9f54b161f7040fed3d72a0c672ee8d3d Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 13:22:42 -0300 Subject: [PATCH 28/33] chore: address comments --- core/src/build/revapi-differences.json | 24 +++ .../migration/v8/SortingMigrationRecipe.java | 129 +++++++++++++++ .../resources/META-INF/rewrite/ToLatest.yml | 92 +---------- .../v8/SortingMigrationRecipeTest.java | 152 ++++++++++++++++++ 4 files changed, 306 insertions(+), 91 deletions(-) create mode 100644 migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java create mode 100644 migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index 092fe759a2..537f4e3860 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -426,6 +426,30 @@ "oldValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"sorterWeightFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "newValue": "{\"id\", \"mimicSelectorRef\", \"downcastEntityClass\", \"variableName\", \"cacheType\", \"selectionOrder\", \"nearbySelectionConfig\", \"filterClass\", \"sorterManner\", \"sorterComparatorClass\", \"comparatorClass\", \"sorterWeightFactoryClass\", \"comparatorFactoryClass\", \"sorterOrder\", \"sorterClass\", \"probabilityWeightFactoryClass\", \"selectedCountLimit\"}", "justification": "New comparator properties" + }, + { + "ignore": true, + "code": "java.annotation.added", + "old": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "new": "class ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig", + "annotation": "@org.jspecify.annotations.NullMarked", + "justification": "Update config" + }, + { + "ignore": true, + "code": "java.annotation.added", + "old": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig>", + "annotation": "@org.jspecify.annotations.NullMarked", + "justification": "Update config" + }, + { + "ignore": true, + "code": "java.annotation.added", + "old": "class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig", + "new": "class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig", + "annotation": "@org.jspecify.annotations.NullMarked", + "justification": "Update config" } ] } diff --git a/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java b/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java new file mode 100644 index 0000000000..cb8fdabd18 --- /dev/null +++ b/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java @@ -0,0 +1,129 @@ +package ai.timefold.solver.migration.v8; + +import java.util.List; + +import ai.timefold.solver.migration.AbstractRecipe; + +import org.openrewrite.Recipe; +import org.openrewrite.java.ChangeAnnotationAttributeName; +import org.openrewrite.java.ChangeMethodName; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.ReplaceConstantWithAnotherConstant; + +public class SortingMigrationRecipe extends AbstractRecipe { + @Override + public String getDisplayName() { + return "Use non-deprecated related sorting fields and methods"; + } + + @Override + public String getDescription() { + return "Use non-deprecated related sorting fields and methods."; + } + + @Override + public List getRecipeList() { + return List.of( + // Update ComparatorFactory + new ChangeMethodName( + "ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory createSorterWeight(..)", + "createSorter", true, null), + new ChangeType("ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory", + "ai.timefold.solver.core.api.domain.common.ComparatorFactory", true), + // Update PlanningVariable sorting fields + new ChangeAnnotationAttributeName("ai.timefold.solver.core.api.domain.variable.PlanningVariable", + "strengthComparatorClass", "comparatorClass"), + new ChangeAnnotationAttributeName("ai.timefold.solver.core.api.domain.variable.PlanningVariable", + "strengthWeightFactoryClass", "comparatorFactoryClass"), + new ChangeType("ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthComparator", + "ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparator", true), + new ChangeType("ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthWeightFactory", + "ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory", true), + // Update PlanningEntity sorting fields + new ChangeAnnotationAttributeName("ai.timefold.solver.core.api.domain.entity.PlanningEntity", + "difficultyComparatorClass", "comparatorClass"), + new ChangeAnnotationAttributeName("ai.timefold.solver.core.api.domain.entity.PlanningEntity", + "difficultyWeightFactoryClass", "comparatorFactoryClass"), + new ChangeType("ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyComparator", + "ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparator", true), + new ChangeType("ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyWeightFactory", + "ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparatorFactory", true), + // Update MoveSelectorConfig sorting methods + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig getSorterComparatorClass(..)", + "getComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig setSorterComparatorClass(..)", + "setComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig withSorterComparatorClass(..)", + "withComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig getSorterWeightFactoryClass(..)", + "getComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig setSorterWeightFactoryClass(..)", + "setComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig withSorterWeightFactoryClass(..)", + "withComparatorFactoryClass", true, null), + // Update EntitySelectorConfig sorting methods + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig getSorterComparatorClass(..)", + "getComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig setSorterComparatorClass(..)", + "setComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig withSorterComparatorClass(..)", + "withComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig getSorterWeightFactoryClass(..)", + "getComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig setSorterWeightFactoryClass(..)", + "setComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig withSorterWeightFactoryClass(..)", + "withComparatorFactoryClass", true, null), + // Update ValueSelectorConfig sorting methods + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig getSorterComparatorClass(..)", + "getComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig setSorterComparatorClass(..)", + "setComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig withSorterComparatorClass(..)", + "withComparatorClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig getSorterWeightFactoryClass(..)", + "getComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig setSorterWeightFactoryClass(..)", + "setComparatorFactoryClass", true, null), + new ChangeMethodName( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig withSorterWeightFactoryClass(..)", + "withComparatorFactoryClass", true, null), + // Update EntitySorterManner + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY", + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING"), + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE", + "ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING_IF_AVAILABLE"), + // Update ValueSorterManner + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH", + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING"), + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE", + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING_IF_AVAILABLE"), + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH", + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING"), + new ReplaceConstantWithAnotherConstant( + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE", + "ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING_IF_AVAILABLE")); + } +} diff --git a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index 26f658d5d3..8bb9c7de32 100644 --- a/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -34,96 +34,6 @@ recipeList: - ai.timefold.solver.migration.v8.AsConstraintRecipe - ai.timefold.solver.migration.v8.RemoveConstraintPackageRecipe - ai.timefold.solver.migration.v8.SolutionManagerRecommendAssignmentRecipe - - org.openrewrite.java.ChangeMethodName: - matchOverrides: true - methodPattern: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory createSorterWeight(..) - newMethodName: createSorter - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.common.ComparatorFactory - ignoreDefinition: true - - org.openrewrite.java.ChangeAnnotationAttributeName: - annotationType: ai.timefold.solver.core.api.domain.variable.PlanningVariable - oldAttributeName: strengthComparatorClass - newAttributeName: comparatorClass - - org.openrewrite.java.ChangeAnnotationAttributeName: - annotationType: ai.timefold.solver.core.api.domain.variable.PlanningVariable - oldAttributeName: strengthWeightFactoryClass - newAttributeName: comparatorFactoryClass - - org.openrewrite.java.ChangeAnnotationAttributeName: - annotationType: ai.timefold.solver.core.api.domain.entity.PlanningEntity - oldAttributeName: difficultyComparatorClass - newAttributeName: comparatorClass - - org.openrewrite.java.ChangeAnnotationAttributeName: - annotationType: ai.timefold.solver.core.api.domain.entity.PlanningEntity - oldAttributeName: difficultyWeightFactoryClass - newAttributeName: comparatorFactoryClass - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthComparator - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparator - ignoreDefinition: true - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullStrengthWeightFactory - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory - ignoreDefinition: true - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyComparator - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparator - ignoreDefinition: true - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullDifficultyWeightFactory - newFullyQualifiedTypeName: ai.timefold.solver.core.api.domain.entity.PlanningEntity.NullComparatorFactory - ignoreDefinition: true - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig getSorterWeightFactoryClass(..) - newMethodName: getComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig setSorterWeightFactoryClass(..) - newMethodName: setComparatorFactoryClass - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig withSorterWeightFactoryClass(..) - newMethodName: withComparatorFactoryClass - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING - ignoreDefinition: true - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner.DESCENDING_IF_AVAILABLE - ignoreDefinition: true - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING - ignoreDefinition: true - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.DESCENDING_IF_AVAILABLE - ignoreDefinition: true - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING - ignoreDefinition: true - - org.openrewrite.java.ReplaceConstantWithAnotherConstant: - existingFullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE - fullyQualifiedConstantName: ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner.ASCENDING_IF_AVAILABLE - ignoreDefinition: true + - ai.timefold.solver.migration.v8.SortingMigrationRecipe - org.openrewrite.java.RemoveUnusedImports - ai.timefold.solver.migration.ChangeVersion diff --git a/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java new file mode 100644 index 0000000000..34df5ca737 --- /dev/null +++ b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java @@ -0,0 +1,152 @@ +package ai.timefold.solver.migration.v8; + +import static org.openrewrite.java.Assertions.java; + +import ai.timefold.solver.migration.AbstractRecipe; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +@Execution(ExecutionMode.CONCURRENT) +class SortingMigrationRecipeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new SortingMigrationRecipe()) + .parser(AbstractRecipe.JAVA_PARSER); + } + + @Test + void constraintMethods() { + runTest( + """ + changeMoveConfig.withSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + changeMoveConfig.withSorterComparatorClass(Comparator.class); + changeMoveConfig.setSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + changeMoveConfig.setSorterComparatorClass(Comparator.class); + changeMoveConfig.getSorterWeightFactoryClass(); + changeMoveConfig.getSorterComparatorClass(); + swapMoveConfig.withSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + swapMoveConfig.withSorterComparatorClass(Comparator.class); + swapMoveConfig.setSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + swapMoveConfig.setSorterComparatorClass(Comparator.class); + swapMoveConfig.getSorterWeightFactoryClass(); + swapMoveConfig.getSorterComparatorClass(); + entityConfig.withSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + entityConfig.withSorterComparatorClass(Comparator.class); + entityConfig.setSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + entityConfig.setSorterComparatorClass(Comparator.class); + entityConfig.getSorterWeightFactoryClass(); + entityConfig.getSorterComparatorClass(); + entityConfig.setSorterManner(EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE); + entityConfig.setSorterManner(EntitySorterManner.DECREASING_DIFFICULTY); + valueConfig.withSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + valueConfig.withSorterComparatorClass(Comparator.class); + valueConfig.setSorterWeightFactoryClass(SelectionSorterWeightFactory.class); + valueConfig.setSorterComparatorClass(Comparator.class); + valueConfig.getSorterWeightFactoryClass(); + valueConfig.getSorterComparatorClass(); + valueConfig.setSorterManner(ValueSorterManner.INCREASING_STRENGTH); + valueConfig.setSorterManner(ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE); + valueConfig.setSorterManner(ValueSorterManner.DECREASING_STRENGTH); + valueConfig.setSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE);""", + """ + changeMoveConfig.withComparatorFactoryClass(ComparatorFactory.class); + changeMoveConfig.withComparatorClass(Comparator.class); + changeMoveConfig.setComparatorFactoryClass(ComparatorFactory.class); + changeMoveConfig.setComparatorClass(Comparator.class); + changeMoveConfig.getComparatorFactoryClass(); + changeMoveConfig.getComparatorClass(); + swapMoveConfig.withComparatorFactoryClass(ComparatorFactory.class); + swapMoveConfig.withComparatorClass(Comparator.class); + swapMoveConfig.setComparatorFactoryClass(ComparatorFactory.class); + swapMoveConfig.setComparatorClass(Comparator.class); + swapMoveConfig.getComparatorFactoryClass(); + swapMoveConfig.getComparatorClass(); + entityConfig.withComparatorFactoryClass(ComparatorFactory.class); + entityConfig.withComparatorClass(Comparator.class); + entityConfig.setComparatorFactoryClass(ComparatorFactory.class); + entityConfig.setComparatorClass(Comparator.class); + entityConfig.getComparatorFactoryClass(); + entityConfig.getComparatorClass(); + entityConfig.setSorterManner(EntitySorterManner.DESCENDING_IF_AVAILABLE); + entityConfig.setSorterManner(EntitySorterManner.DESCENDING); + valueConfig.withComparatorFactoryClass(ComparatorFactory.class); + valueConfig.withComparatorClass(Comparator.class); + valueConfig.setComparatorFactoryClass(ComparatorFactory.class); + valueConfig.setComparatorClass(Comparator.class); + valueConfig.getComparatorFactoryClass(); + valueConfig.getComparatorClass(); + valueConfig.setSorterManner(ValueSorterManner.ASCENDING); + valueConfig.setSorterManner(ValueSorterManner.ASCENDING_IF_AVAILABLE); + valueConfig.setSorterManner(ValueSorterManner.DESCENDING); + valueConfig.setSorterManner(ValueSorterManner.DESCENDING_IF_AVAILABLE);"""); + } + + private void runTest(String contentBefore, String contentAfter) { + rewriteRun(java(adjustBefore(contentBefore), adjustAfter(contentAfter))); + } + + private static String adjustBefore(String content) { + return """ + import java.util.Comparator; + import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; + import ai.timefold.solver.core.api.domain.entity.PlanningEntity; + import ai.timefold.solver.core.api.domain.variable.PlanningVariable; + 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.move.generic.ChangeMoveSelectorConfig; + 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.config.heuristic.selector.value.ValueSorterManner; + + @PlanningEntity(difficultyWeightFactoryClass = PlanningEntity.NullDifficultyWeightFactory.class, difficultyComparatorClass = PlanningEntity.NullDifficultyComparator.class) + public class Test implements SelectionSorterWeightFactory { + @PlanningVariable(strengthComparatorClass = PlanningVariable.NullStrengthComparator.class) + private Object value; + @PlanningVariable(strengthWeightFactoryClass = PlanningVariable.NullStrengthWeightFactory.class) + private Object value2; + public void validate(ChangeMoveSelectorConfig changeMoveConfig, ListSwapMoveSelectorConfig swapMoveConfig, EntitySelectorConfig entityConfig, ValueSelectorConfig valueConfig) { + %8s%s + } + @Override + public Comparable createSorterWeight(Object o, Object o2) { return null; } + }""" + .formatted("", content); + } + + private static String adjustAfter(String content) { + return """ + import java.util.Comparator; + + import ai.timefold.solver.core.api.domain.common.ComparatorFactory; + import ai.timefold.solver.core.api.domain.entity.PlanningEntity; + import ai.timefold.solver.core.api.domain.variable.PlanningVariable; + import ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparator; + import ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory; + 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.move.generic.ChangeMoveSelectorConfig; + 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.config.heuristic.selector.value.ValueSorterManner; + + @PlanningEntity(comparatorFactoryClass = PlanningEntity.NullComparatorFactory.class, comparatorClass = PlanningEntity.NullComparator.class) + public class Test implements ComparatorFactory { + @PlanningVariable(comparatorClass = NullComparator.class) + private Object value; + @PlanningVariable(comparatorFactoryClass = NullComparatorFactory.class) + private Object value2; + public void validate(ChangeMoveSelectorConfig changeMoveConfig, ListSwapMoveSelectorConfig swapMoveConfig, EntitySelectorConfig entityConfig, ValueSelectorConfig valueConfig) { + %8s%s + } + @Override + public Comparable createSorter(Object o, Object o2) { return null; } + }""" + .formatted("", content); + } + +} From e09cb52c540fe4320bdff8524df445e9aea32a03 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 13:34:01 -0300 Subject: [PATCH 29/33] chore: address comments --- .../modules/ROOT/pages/optimization-algorithms/overview.adoc | 2 +- .../using-timefold-solver/modeling-planning-problems.adoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 4f82411a68..2925fa6e02 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1601,7 +1601,7 @@ The solver may choose to reuse them in different contexts. [#sortedSelectionByComparatorFactory] -===== Sorted selection by `ComparatorFactory` +===== [[sortedSelectionBySelectionSorterWeightFactory]]Sorted selection by `ComparatorFactory` If you need the entire solution to sort a ``Selector``, use a `ComparatorFactory` instead: diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc index eb0cae156c..6cfb90709b 100644 --- a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc +++ b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc @@ -373,7 +373,7 @@ As Java's `enum` and `record` types are immutable by design, these can not be us [#planningEntitySorting] -=== Planning entity sorting +=== [[planningEntityDifficulty]]Planning entity sorting Some optimization algorithms are more efficient when planning entities are sorted based on a specific metric, which helps estimate the relative difficulty of each planning entity. @@ -1433,7 +1433,7 @@ The `ValueRangeFactory` has creation methods for several value class types: [#planningValueSorting] -=== Planning value sorting +=== [[planningValueStrength]]Planning value sorting Some optimization algorithms work a bit more efficiently if the planning values are sorted according to a given metric, From c8ffe30c1bc610fa1f09ceeb2bcc2fa0fa5d363a Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 15:06:58 -0300 Subject: [PATCH 30/33] chore: address comments --- .../api/domain/common/ComparatorFactory.java | 7 ++++--- .../api/domain/entity/PlanningEntity.java | 8 +++++--- .../api/domain/variable/PlanningVariable.java | 8 +++++--- .../selector/entity/EntitySelectorConfig.java | 9 +++++---- .../selector/move/MoveSelectorConfig.java | 9 +++++---- .../selector/value/ValueSelectorConfig.java | 9 +++++---- .../entity/descriptor/EntityDescriptor.java | 13 ++++++++++-- .../descriptor/BasicVariableDescriptor.java | 5 ++--- .../descriptor/GenuineVariableDescriptor.java | 19 +++++++++++------- .../decorator/ComparatorFactoryAdapter.java | 17 ++++++++++++++++ .../decorator/FactorySelectionSorter.java | 4 ++-- .../SelectionSorterWeightFactory.java | 15 +++++++------- .../entity/EntitySelectorFactory.java | 19 ++++++++++++------ .../move/AbstractMoveSelectorFactory.java | 20 ++++++++++++------- .../selector/value/ValueSelectorFactory.java | 16 +++++++++++---- .../decorator/FactorySelectionSorterTest.java | 4 ++-- .../entity/EntitySelectorFactoryTest.java | 12 +++++++++-- .../decorator/SortingMoveSelectorTest.java | 4 ++-- .../value/ValueSelectorFactoryTest.java | 14 +++++++++---- .../testdomain/common/DummyValueFactory.java | 13 +++++++++--- .../common/TestSortableFactory.java | 5 +++-- ...stdataObjectSortableDescendingFactory.java | 6 +++--- .../TestdataDifficultyFactory.java | 10 +++++++++- 23 files changed, 167 insertions(+), 79 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java index fefc2f11d8..ba5743ca51 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java @@ -27,20 +27,21 @@ * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type - * + * @param the returning type + * * @see ValueSelectorConfig * @see EntitySelectorConfig * @see ConstructionHeuristicPhaseConfig */ @NullMarked @FunctionalInterface -public interface ComparatorFactory { +public interface ComparatorFactory> { /** * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} */ - Comparable createSorter(Solution_ solution, T selection); + V createSorter(Solution_ solution, T selection); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index 7ecf4da8c1..6fa2316b4d 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -10,6 +10,7 @@ import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that the class is a planning entity. @@ -62,7 +63,7 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - interface NullComparatorFactory extends ComparatorFactory { + interface NullComparatorFactory> extends ComparatorFactory { } /** @@ -128,7 +129,7 @@ interface NullDifficultyComparator extends NullComparator { * @see #difficultyComparatorClass() */ @Deprecated(forRemoval = true, since = "1.28.0") - Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; + Class difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class; /** * Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. @@ -136,7 +137,8 @@ interface NullDifficultyComparator extends NullComparator { * @deprecated Deprecated in favor of {@link NullComparatorFactory}. */ @Deprecated(forRemoval = true, since = "1.28.0") - interface NullDifficultyWeightFactory extends NullComparatorFactory { + interface NullDifficultyWeightFactory> + extends SelectionSorterWeightFactory, NullComparatorFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 70391dd81f..9e7b8e60ae 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -12,6 +12,7 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms. @@ -86,7 +87,7 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - interface NullComparatorFactory extends ComparatorFactory { + interface NullComparatorFactory> extends ComparatorFactory { } /** @@ -137,7 +138,7 @@ interface NullStrengthComparator extends NullComparator { * @see #strengthComparatorClass() */ @Deprecated(forRemoval = true, since = "1.28.0") - Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; + Class strengthWeightFactoryClass() default NullStrengthWeightFactory.class; /** * Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. @@ -145,6 +146,7 @@ interface NullStrengthComparator extends NullComparator { * @deprecated Deprecated in favor of {@link NullComparatorFactory}. */ @Deprecated(forRemoval = true, since = "1.28.0") - interface NullStrengthWeightFactory extends NullComparatorFactory { + interface NullStrengthWeightFactory> + extends SelectionSorterWeightFactory, NullComparatorFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java index 38ddada995..5e8c52e316 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java @@ -19,6 +19,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -85,7 +86,7 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe */ @Deprecated(forRemoval = true, since = "1.28.0") @Nullable - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; @Nullable protected Class comparatorFactoryClass = null; @Nullable @@ -204,7 +205,7 @@ public void setComparatorClass(@Nullable Class comparatorC * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } @@ -213,7 +214,7 @@ public void setComparatorClass(@Nullable Class comparatorC * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -322,7 +323,7 @@ public EntitySelectorConfig withComparatorClass(Class comp */ @Deprecated(forRemoval = true, since = "1.28.0") public EntitySelectorConfig - withSorterWeightFactoryClass(Class weightFactoryClass) { + withSorterWeightFactoryClass(Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index 4d7454035b..b550290069 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -34,6 +34,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -100,7 +101,7 @@ public abstract class MoveSelectorConfig sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; @Nullable protected Class comparatorFactoryClass = null; @Nullable @@ -173,7 +174,7 @@ public void setComparatorClass(Class comparatorClass) { * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } @@ -182,7 +183,7 @@ public void setComparatorClass(Class comparatorClass) { * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -274,7 +275,7 @@ public Config_ withComparatorClass(Class comparatorClass) */ @Deprecated(forRemoval = true, since = "1.28.0") public Config_ withSorterWeightFactoryClass( - Class sorterWeightFactoryClass) { + Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; return (Config_) this; } diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java index 705f6ee88b..0477d37d57 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java @@ -19,6 +19,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -85,7 +86,7 @@ public class ValueSelectorConfig extends SelectorConfig { */ @Deprecated(forRemoval = true, since = "1.28.0") @Nullable - protected Class sorterWeightFactoryClass = null; + protected Class sorterWeightFactoryClass = null; @Nullable protected Class comparatorFactoryClass = null; @Nullable @@ -212,7 +213,7 @@ public void setComparatorClass(@Nullable Class comparatorC * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()} */ @Deprecated(forRemoval = true, since = "1.28.0") - public @Nullable Class getSorterWeightFactoryClass() { + public @Nullable Class getSorterWeightFactoryClass() { return sorterWeightFactoryClass; } @@ -221,7 +222,7 @@ public void setComparatorClass(@Nullable Class comparatorC * @param sorterWeightFactoryClass the class */ @Deprecated(forRemoval = true, since = "1.28.0") - public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { + public void setSorterWeightFactoryClass(@Nullable Class sorterWeightFactoryClass) { this.sorterWeightFactoryClass = sorterWeightFactoryClass; } @@ -335,7 +336,7 @@ public ValueSelectorConfig withComparatorClass(Class compa */ @Deprecated(forRemoval = true, since = "1.28.0") public ValueSelectorConfig - withSorterWeightFactoryClass(Class weightFactoryClass) { + withSorterWeightFactoryClass(Class weightFactoryClass) { this.setSorterWeightFactoryClass(weightFactoryClass); return this; } 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 a2519a7af7..a27f9c991d 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 @@ -22,6 +22,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PinningFilter; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.entity.PlanningPin; @@ -63,9 +64,11 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.move.director.MoveDirector; import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; import ai.timefold.solver.core.impl.util.CollectionUtils; @@ -303,7 +306,7 @@ private void processSorting(PlanningEntity entityAnnotation) { var selectedComparatorPropertyName = "comparatorClass"; var selectedComparatorClass = comparatorClass; var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; - var selectedComparatorFactoryClass = comparatorFactoryClass; + Class selectedComparatorFactoryClass = comparatorFactoryClass; if (difficultyComparatorClass != null) { selectedComparatorPropertyName = "difficultyComparatorClass"; selectedComparatorClass = difficultyComparatorClass; @@ -322,8 +325,14 @@ private void processSorting(PlanningEntity entityAnnotation) { descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); } if (selectedComparatorFactoryClass != null) { - var comparatorFactory = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, + var instance = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass); + ComparatorFactory comparatorFactory; + if (instance instanceof ComparatorFactory factoryInstance) { + comparatorFactory = (ComparatorFactory) factoryInstance; + } else { + comparatorFactory = new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); + } descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java index cd388b32be..6777b3f903 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java @@ -2,7 +2,6 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; @@ -87,7 +86,7 @@ private SortingProperties assertSortingProperties(PlanningVariable planningVaria var selectedComparatorPropertyName = "comparatorClass"; var selectedComparatorClass = comparatorClass; var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; - var selectedComparatorFactoryClass = comparatorFactoryClass; + Class selectedComparatorFactoryClass = comparatorFactoryClass; if (strengthComparatorClass != null) { selectedComparatorPropertyName = "strengthComparatorClass"; selectedComparatorClass = strengthComparatorClass; @@ -187,7 +186,7 @@ public SelectionFilter getMovableChainedTrailingValueFilter() } private record SortingProperties(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index df7b9ad0bb..46cb75577b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -20,9 +20,11 @@ import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * @param the solution type, the class with the {@link PlanningSolution} annotation @@ -157,7 +159,7 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli @SuppressWarnings("rawtypes") protected void processSorting(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) { comparatorClass = null; } @@ -179,12 +181,15 @@ protected void processSorting(String comparatorPropertyName, Class comparatorFactory = - newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); - ascendingSorter = new FactorySelectionSorter<>(comparatorFactory, - SelectionSorterOrder.ASCENDING); - descendingSorter = new FactorySelectionSorter<>(comparatorFactory, - SelectionSorterOrder.DESCENDING); + var instance = newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); + ComparatorFactory comparatorFactory; + if (instance instanceof ComparatorFactory factoryInstance) { + comparatorFactory = (ComparatorFactory) factoryInstance; + } else { + comparatorFactory = new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); + } + ascendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); + descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java new file mode 100644 index 0000000000..da2dbe6f05 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; + +public class ComparatorFactoryAdapter> implements ComparatorFactory { + + private final SelectionSorterWeightFactory sorterWeightFactory; + + public ComparatorFactoryAdapter(SelectionSorterWeightFactory sorterWeightFactory) { + this.sorterWeightFactory = sorterWeightFactory; + } + + @Override + public V createSorter(Solution_ solution, T selection) { + return (V) sorterWeightFactory.createSorterWeight(solution, selection); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index 29f8eecf81..18da79310c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -23,10 +23,10 @@ */ public final class FactorySelectionSorter implements SelectionSorter { - private final ComparatorFactory selectionComparatorFactory; + private final ComparatorFactory> selectionComparatorFactory; private final Comparator appliedComparator; - public FactorySelectionSorter(ComparatorFactory selectionComparatorFactory, + public FactorySelectionSorter(ComparatorFactory> selectionComparatorFactory, SelectionSorterOrder selectionSorterOrder) { this.selectionComparatorFactory = selectionComparatorFactory; switch (selectionSorterOrder) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index dca0bbc505..3305d40fda 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -22,15 +22,14 @@ * @param the selection type */ @Deprecated(forRemoval = true, since = "1.28.0") -public interface SelectionSorterWeightFactory extends ComparatorFactory { - - Comparable createSorterWeight(Solution_ solution, T selection); +@FunctionalInterface +public interface SelectionSorterWeightFactory { /** - * The default implementation has been created to maintain compatibility with the old contract. + * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to + * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} + * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} */ - @Override - default Comparable createSorter(Solution_ solution, T selection) { - return createSorterWeight(solution, selection); - } + Comparable createSorterWeight(Solution_ solution, T selection); + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 8903f0141d..86b288a4d0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -19,11 +19,13 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.ValueRangeRecorderId; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByValueSelector; @@ -217,7 +219,7 @@ private static String determineComparatorFactoryPropertyName(EntitySelectorConfi return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } - private static Class + private static Class determineComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(entitySelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { @@ -368,11 +370,16 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), - comparatorFactoryClass); - sorter = new FactorySelectionSorter<>(comparatorFactory, - SelectionSorterOrder.resolve(config.getSorterOrder())); + var instance = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + comparatorFactoryClass); + ComparatorFactory comparatorFactory; + if (instance instanceof ComparatorFactory factoryInstance) { + comparatorFactory = (ComparatorFactory) factoryInstance; + } else { + comparatorFactory = + new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); + } + sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 797b0efb94..b3b7abbc40 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -11,11 +11,13 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.CachingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector; @@ -153,8 +155,7 @@ private String determineComparatorFactoryPropertyName(MoveSelectorConfig_ moveSe return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } - private Class - determineComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { + private Class determineComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(moveSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return moveSelectorConfig.getSorterWeightFactoryClass(); @@ -243,11 +244,16 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - ComparatorFactory> comparatorFactory = - ConfigUtils.newInstance(config, determineComparatorFactoryPropertyName(config), - comparatorFactoryClass); - sorter = new FactorySelectionSorter<>(comparatorFactory, - SelectionSorterOrder.resolve(config.getSorterOrder())); + var instance = + ConfigUtils.newInstance(config, determineComparatorFactoryPropertyName(config), comparatorFactoryClass); + ComparatorFactory, ?> comparatorFactory; + if (instance instanceof ComparatorFactory factoryInstance) { + comparatorFactory = (ComparatorFactory, ?>) factoryInstance; + } else { + comparatorFactory = + new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory>) instance); + } + sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 1b06063fed..eec234d9b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -18,11 +18,13 @@ 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.AbstractSelectorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.CachingValueSelector; @@ -249,7 +251,7 @@ private static String determineComparatorFactoryPropertyName(ValueSelectorConfig return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } - private static Class + private static Class determineComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(valueSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { @@ -387,9 +389,15 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - ComparatorFactory comparatorFactory = - instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), - comparatorFactoryClass); + var instance = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + comparatorFactoryClass); + ComparatorFactory comparatorFactory; + if (instance instanceof ComparatorFactory factoryInstance) { + comparatorFactory = (ComparatorFactory) factoryInstance; + } else { + comparatorFactory = + new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); + } sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java index eea4e25c6d..0649810c1a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java @@ -18,7 +18,7 @@ class FactorySelectionSorterTest { @Test void sortAscending() { - ComparatorFactory weightFactory = (solution, selection) -> Integer + ComparatorFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.ASCENDING); @@ -34,7 +34,7 @@ void sortAscending() { @Test void sortDescending() { - ComparatorFactory weightFactory = (solution, selection) -> Integer + ComparatorFactory weightFactory = (solution, selection) -> Integer .valueOf(selection.getCode().charAt(0)); FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( weightFactory, SelectionSorterOrder.DESCENDING); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 508addf5ae..57758fbdf5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -16,6 +16,7 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SortingEntitySelector; @@ -243,11 +244,18 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionComparatorFactory - implements ComparatorFactory { + implements SelectionSorterWeightFactory, + ComparatorFactory { + @Override - public Comparable createSorter(TestdataSolution testdataSolution, TestdataEntity selection) { + public Integer createSorter(TestdataSolution testdataSolution, TestdataEntity selection) { return 0; } + + @Override + public Comparable createSorterWeight(TestdataSolution solution, TestdataEntity selection) { + return createSorter(solution, selection); + } } public static class DummyEntityComparator implements Comparator { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index 942cb72c35..d0b8e7e849 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -199,7 +199,7 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPo } public static class TestCodeAssertableComparatorFactory - implements SelectionSorterWeightFactory, ComparatorFactory { + implements SelectionSorterWeightFactory, ComparatorFactory { @Override public Comparable createSorterWeight(Object o, CodeAssertable selection) { @@ -207,7 +207,7 @@ public Comparable createSorterWeight(Object o, CodeAssertable selection) { } @Override - public Comparable createSorter(Object o, CodeAssertable selection) { + public String createSorter(Object o, CodeAssertable selection) { return selection.getCode(); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 7ebd3936ca..d8d804639c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -23,6 +23,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.ProbabilityValueSelector; @@ -103,8 +104,7 @@ void phaseRandom() { ValueSelector valueSelector = ValueSelectorFactory.create(valueSelectorConfig).buildValueSelector(configPolicy, entityDescriptor, SelectionCacheType.JUST_IN_TIME, SelectionOrder.RANDOM); assertThat(valueSelector) - .isInstanceOf(IterableFromSolutionPropertyValueSelector.class); - assertThat(valueSelector) + .isInstanceOf(IterableFromSolutionPropertyValueSelector.class) .isNotInstanceOf(ShufflingValueSelector.class); assertThat(valueSelector.getCacheType()).isEqualTo(SelectionCacheType.PHASE); } @@ -352,11 +352,17 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionComparatorFactory - implements ComparatorFactory { + implements SelectionSorterWeightFactory, + ComparatorFactory { @Override - public Comparable createSorter(TestdataSolution testdataSolution, TestdataValue selection) { + public Integer createSorter(TestdataSolution testdataSolution, TestdataValue selection) { return 0; } + + @Override + public Comparable createSorterWeight(TestdataSolution solution, TestdataValue selection) { + return createSorter(solution, selection); + } } public static class DummyValueComparator implements Comparator { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java index fd938bed37..d04ab9a1e5 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java @@ -1,14 +1,21 @@ package ai.timefold.solver.core.testdomain.common; import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; public class DummyValueFactory - implements ComparatorFactory { + implements ComparatorFactory, + SelectionSorterWeightFactory { @Override - public Comparable createSorter(TestdataListFactorySortableSolution solution, TestdataValue selection) { - return v -> 0; + public Integer createSorter(TestdataListFactorySortableSolution solution, TestdataValue selection) { + return 0; + } + + @Override + public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, TestdataValue selection) { + return createSorter(solution, selection); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java index fe0fbe5336..1a8ca4b28c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java @@ -4,14 +4,15 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; public class TestSortableFactory - implements SelectionSorterWeightFactory, ComparatorFactory { + implements SelectionSorterWeightFactory, + ComparatorFactory { @Override public Comparable createSorterWeight(Object o, TestSortableObject selection) { return selection; } @Override - public Comparable createSorter(Object o, TestSortableObject selection) { + public TestSortableObject createSorter(Object o, TestSortableObject selection) { return selection; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java index d568641974..27df5adc6c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java @@ -4,8 +4,8 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataObjectSortableDescendingFactory implements SelectionSorterWeightFactory, - ComparatorFactory { +public class TestdataObjectSortableDescendingFactory + implements ComparatorFactory, SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(Object solution, TestdataObject selection) { @@ -13,7 +13,7 @@ public Comparable createSorterWeight(Object solution, TestdataObject selection) } @Override - public Comparable createSorter(Object solution, TestdataObject selection) { + public Integer createSorter(Object solution, TestdataObject selection) { // Descending order return -extractCode(selection.getCode()); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java index 0f1e51f720..4e26dc9642 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java @@ -1,9 +1,11 @@ package ai.timefold.solver.core.testdomain.difficultyweight; import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; public class TestdataDifficultyFactory implements - ComparatorFactory { + ComparatorFactory, + SelectionSorterWeightFactory { @Override public TestdataDifficultyWeightComparable createSorter(TestdataDifficultyWeightSolution solution, @@ -11,6 +13,12 @@ public TestdataDifficultyWeightComparable createSorter(TestdataDifficultyWeightS return new TestdataDifficultyWeightComparable(); } + @Override + public Comparable createSorterWeight(TestdataDifficultyWeightSolution testdataDifficultyWeightSolution, + TestdataDifficultyWeightEntity selection) { + return createSorter(testdataDifficultyWeightSolution, selection); + } + public static class TestdataDifficultyWeightComparable implements Comparable { @Override From 78d417c3329fb8c49702d7e5b43cef9dc6d1489f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 22 Oct 2025 20:01:30 -0300 Subject: [PATCH 31/33] chore: address sonar --- .../config/heuristic/selector/move/MoveSelectorConfig.java | 4 ++-- .../selector/common/decorator/FactorySelectionSorter.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java index b550290069..291bca6620 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java @@ -162,11 +162,11 @@ public void setSorterComparatorClass(@Nullable Class sorte this.sorterComparatorClass = sorterComparatorClass; } - public Class getComparatorClass() { + public @Nullable Class getComparatorClass() { return comparatorClass; } - public void setComparatorClass(Class comparatorClass) { + public void setComparatorClass(@Nullable Class comparatorClass) { this.comparatorClass = comparatorClass; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java index 18da79310c..8ddf49e56d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java @@ -24,14 +24,14 @@ public final class FactorySelectionSorter implements SelectionSorter { private final ComparatorFactory> selectionComparatorFactory; - private final Comparator appliedComparator; + private final Comparator> appliedComparator; public FactorySelectionSorter(ComparatorFactory> selectionComparatorFactory, SelectionSorterOrder selectionSorterOrder) { this.selectionComparatorFactory = selectionComparatorFactory; switch (selectionSorterOrder) { case ASCENDING: - this.appliedComparator = Comparator.naturalOrder(); + this.appliedComparator = (Comparator>) Comparator.naturalOrder(); break; case DESCENDING: this.appliedComparator = Collections.reverseOrder(); From bcb4e0831055f0a33d578ff761f3a93e0b2e80a1 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 23 Oct 2025 12:22:59 -0300 Subject: [PATCH 32/33] chore: address comments --- .../api/domain/common/ComparatorFactory.java | 10 +-- .../api/domain/entity/PlanningEntity.java | 10 ++- .../api/domain/variable/PlanningVariable.java | 9 +- .../entity/descriptor/EntityDescriptor.java | 17 +--- .../descriptor/BasicVariableDescriptor.java | 8 +- .../descriptor/GenuineVariableDescriptor.java | 25 ++---- .../decorator/ComparatorFactoryAdapter.java | 17 ---- .../ComparatorFactorySelectionSorter.java | 57 +++++++++++++ .../decorator/FactorySelectionSorter.java | 82 ------------------- .../SelectionSorterWeightFactory.java | 11 ++- .../entity/EntitySelectorFactory.java | 20 ++--- .../move/AbstractMoveSelectorFactory.java | 20 ++--- .../selector/value/ValueSelectorFactory.java | 26 ++---- ...DefaultConstructionHeuristicPhaseTest.java | 16 ++-- ...ComparatorFactorySelectionSorterTest.java} | 21 +++-- .../entity/EntitySelectorFactoryTest.java | 11 +-- .../decorator/SortingMoveSelectorTest.java | 13 +-- .../value/ValueSelectorFactoryTest.java | 10 +-- .../testdomain/common/DummyValueFactory.java | 12 +-- .../common/TestSortableFactory.java | 10 +-- ...stdataObjectSortableDescendingFactory.java | 9 +- .../TestdataDifficultyFactory.java | 22 +---- .../optimization-algorithms/overview.adoc | 2 +- .../migration/v8/SortingMigrationRecipe.java | 2 +- .../v8/SortingMigrationRecipeTest.java | 9 +- 25 files changed, 163 insertions(+), 286 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorter.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java rename core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/{FactorySelectionSorterTest.java => ComparatorFactorySelectionSorterTest.java} (66%) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java index ba5743ca51..df604b1c0f 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/ComparatorFactory.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.api.domain.common; +import java.util.Comparator; + import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; @@ -27,7 +29,6 @@ * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the selection type - * @param the returning type * * @see ValueSelectorConfig * @see EntitySelectorConfig @@ -35,13 +36,12 @@ */ @NullMarked @FunctionalInterface -public interface ComparatorFactory> { +public interface ComparatorFactory { /** * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to - * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector} - * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable} + * @return never null */ - V createSorter(Solution_ solution, T selection); + Comparator createComparator(Solution_ solution); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index 6fa2316b4d..383ac1112a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -56,6 +56,9 @@ interface NullComparator extends Comparator { /** * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. *

+ * Differs from {@link #comparatorClass()} + * because it allows accessing the current solution when creating the comparator. + *

* Do not use together with {@link #comparatorClass()}. * * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) @@ -63,7 +66,7 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - interface NullComparatorFactory> extends ComparatorFactory { + interface NullComparatorFactory extends ComparatorFactory { } /** @@ -137,8 +140,9 @@ interface NullDifficultyComparator extends NullComparator { * @deprecated Deprecated in favor of {@link NullComparatorFactory}. */ @Deprecated(forRemoval = true, since = "1.28.0") - interface NullDifficultyWeightFactory> - extends SelectionSorterWeightFactory, NullComparatorFactory { + interface NullDifficultyWeightFactory + extends SelectionSorterWeightFactory, + NullComparatorFactory { } } diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 9e7b8e60ae..fa0fc8227a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -80,6 +80,9 @@ interface NullComparator extends Comparator { /** * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}. *

+ * Differs from {@link #comparatorClass()} + * because it allows accessing the current solution when creating the comparator. + *

* Do not use together with {@link #comparatorClass()}. * * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation) @@ -87,7 +90,7 @@ interface NullComparator extends Comparator { */ Class comparatorFactoryClass() default NullComparatorFactory.class; - interface NullComparatorFactory> extends ComparatorFactory { + interface NullComparatorFactory extends ComparatorFactory { } /** @@ -146,7 +149,7 @@ interface NullStrengthComparator extends NullComparator { * @deprecated Deprecated in favor of {@link NullComparatorFactory}. */ @Deprecated(forRemoval = true, since = "1.28.0") - interface NullStrengthWeightFactory> - extends SelectionSorterWeightFactory, NullComparatorFactory { + interface NullStrengthWeightFactory + extends SelectionSorterWeightFactory, NullComparatorFactory { } } 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 a27f9c991d..91476399ea 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 @@ -22,7 +22,6 @@ import java.util.function.Consumer; import java.util.function.Predicate; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PinningFilter; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.entity.PlanningPin; @@ -64,11 +63,9 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.move.director.MoveDirector; import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; import ai.timefold.solver.core.impl.util.CollectionUtils; @@ -306,7 +303,7 @@ private void processSorting(PlanningEntity entityAnnotation) { var selectedComparatorPropertyName = "comparatorClass"; var selectedComparatorClass = comparatorClass; var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; - Class selectedComparatorFactoryClass = comparatorFactoryClass; + var selectedComparatorFactoryClass = comparatorFactoryClass; if (difficultyComparatorClass != null) { selectedComparatorPropertyName = "difficultyComparatorClass"; selectedComparatorClass = difficultyComparatorClass; @@ -325,15 +322,9 @@ private void processSorting(PlanningEntity entityAnnotation) { descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); } if (selectedComparatorFactoryClass != null) { - var instance = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, + var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass); - ComparatorFactory comparatorFactory; - if (instance instanceof ComparatorFactory factoryInstance) { - comparatorFactory = (ComparatorFactory) factoryInstance; - } else { - comparatorFactory = new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); - } - descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); + descendingSorter = new ComparatorFactorySelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java index 6777b3f903..920cbdc56a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java @@ -2,6 +2,7 @@ import java.util.Comparator; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; @@ -86,7 +87,7 @@ private SortingProperties assertSortingProperties(PlanningVariable planningVaria var selectedComparatorPropertyName = "comparatorClass"; var selectedComparatorClass = comparatorClass; var selectedComparatorFactoryPropertyName = "comparatorFactoryClass"; - Class selectedComparatorFactoryClass = comparatorFactoryClass; + var selectedComparatorFactoryClass = comparatorFactoryClass; if (strengthComparatorClass != null) { selectedComparatorPropertyName = "strengthComparatorClass"; selectedComparatorClass = strengthComparatorClass; @@ -96,8 +97,7 @@ private SortingProperties assertSortingProperties(PlanningVariable planningVaria selectedComparatorFactoryClass = strengthWeightFactoryClass; } return new SortingProperties(selectedComparatorPropertyName, selectedComparatorClass, - selectedComparatorFactoryPropertyName, - selectedComparatorFactoryClass); + selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass); } private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) { @@ -186,7 +186,7 @@ public SelectionFilter getMovableChainedTrailingValueFilter() } private record SortingProperties(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 46cb75577b..0e02da7e89 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -20,11 +20,9 @@ import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; /** * @param the solution type, the class with the {@link PlanningSolution} annotation @@ -159,7 +157,7 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli @SuppressWarnings("rawtypes") protected void processSorting(String comparatorPropertyName, Class comparatorClass, - String comparatorFactoryPropertyName, Class comparatorFactoryClass) { + String comparatorFactoryPropertyName, Class comparatorFactoryClass) { if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) { comparatorClass = null; } @@ -174,22 +172,17 @@ protected void processSorting(String comparatorPropertyName, Class strengthComparator = newInstance(this::toString, comparatorPropertyName, comparatorClass); - ascendingSorter = new ComparatorSelectionSorter<>(strengthComparator, + Comparator comparator = newInstance(this::toString, comparatorPropertyName, comparatorClass); + ascendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.ASCENDING); - descendingSorter = new ComparatorSelectionSorter<>(strengthComparator, + descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); } if (comparatorFactoryClass != null) { - var instance = newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); - ComparatorFactory comparatorFactory; - if (instance instanceof ComparatorFactory factoryInstance) { - comparatorFactory = (ComparatorFactory) factoryInstance; - } else { - comparatorFactory = new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); - } - ascendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); - descendingSorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); + ComparatorFactory comparatorFactory = + newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); + ascendingSorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); + descendingSorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.DESCENDING); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java deleted file mode 100644 index da2dbe6f05..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactoryAdapter.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; - -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; - -public class ComparatorFactoryAdapter> implements ComparatorFactory { - - private final SelectionSorterWeightFactory sorterWeightFactory; - - public ComparatorFactoryAdapter(SelectionSorterWeightFactory sorterWeightFactory) { - this.sorterWeightFactory = sorterWeightFactory; - } - - @Override - public V createSorter(Solution_ solution, T selection) { - return (V) sorterWeightFactory.createSorterWeight(solution, selection); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorter.java new file mode 100644 index 0000000000..0a7483c674 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorter.java @@ -0,0 +1,57 @@ +package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; + +/** + * Sorts a selection {@link List} based on a {@link ComparatorFactory}. + * + * @param the solution type, the class with the {@link PlanningSolution} annotation + * @param the selection type + */ +public final class ComparatorFactorySelectionSorter implements SelectionSorter { + + private final ComparatorFactory selectionComparatorFactory; + private final SelectionSorterOrder selectionSorterOrder; + + public ComparatorFactorySelectionSorter(ComparatorFactory selectionComparatorFactory, + SelectionSorterOrder selectionSorterOrder) { + this.selectionComparatorFactory = selectionComparatorFactory; + this.selectionSorterOrder = selectionSorterOrder; + } + + private Comparator getAppliedComparator(Comparator comparator) { + return switch (selectionSorterOrder) { + case ASCENDING -> comparator; + case DESCENDING -> Collections.reverseOrder(comparator); + }; + } + + @Override + public void sort(ScoreDirector scoreDirector, List selectionList) { + var appliedComparator = + getAppliedComparator(selectionComparatorFactory.createComparator(scoreDirector.getWorkingSolution())); + selectionList.sort(appliedComparator); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ComparatorFactorySelectionSorter that)) { + return false; + } + return Objects.equals(selectionComparatorFactory, that.selectionComparatorFactory) + && selectionSorterOrder == that.selectionSorterOrder; + } + + @Override + public int hashCode() { + return Objects.hash(selectionComparatorFactory, selectionSorterOrder); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java deleted file mode 100644 index 8ddf49e56d..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorter.java +++ /dev/null @@ -1,82 +0,0 @@ -package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.SortedMap; -import java.util.TreeMap; - -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; -import ai.timefold.solver.core.impl.heuristic.move.Move; -import ai.timefold.solver.core.impl.heuristic.selector.Selector; - -/** - * Sorts a selection {@link List} based on a {@link ComparatorFactory}. - * - * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the selection type - */ -public final class FactorySelectionSorter implements SelectionSorter { - - private final ComparatorFactory> selectionComparatorFactory; - private final Comparator> appliedComparator; - - public FactorySelectionSorter(ComparatorFactory> selectionComparatorFactory, - SelectionSorterOrder selectionSorterOrder) { - this.selectionComparatorFactory = selectionComparatorFactory; - switch (selectionSorterOrder) { - case ASCENDING: - this.appliedComparator = (Comparator>) Comparator.naturalOrder(); - break; - case DESCENDING: - this.appliedComparator = Collections.reverseOrder(); - break; - default: - throw new IllegalStateException( - "The selectionSorterOrder (%s) is not implemented.".formatted(selectionSorterOrder)); - } - } - - @Override - public void sort(ScoreDirector scoreDirector, List selectionList) { - sort(scoreDirector.getWorkingSolution(), selectionList); - } - - /** - * @param solution never null, the {@link PlanningSolution} to which the selections belong or apply to - * @param selectionList never null, a {@link List} - * of {@link PlanningEntity}, planningValue, {@link Move} or {@link Selector} - */ - public void sort(Solution_ solution, List selectionList) { - SortedMap, T> selectionMap = new TreeMap<>(appliedComparator); - for (var selection : selectionList) { - var selectionSorter = selectionComparatorFactory.createSorter(solution, selection); - var previous = selectionMap.put(selectionSorter, selection); - if (previous != null) { - throw new IllegalStateException( - "The selectionList contains 2 times the same selection (%s) and (%s).".formatted(previous, selection)); - } - } - selectionList.clear(); - selectionList.addAll(selectionMap.values()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof FactorySelectionSorter that)) { - return false; - } - return Objects.equals(selectionComparatorFactory, that.selectionComparatorFactory) - && Objects.equals(appliedComparator, that.appliedComparator); - } - - @Override - public int hashCode() { - return Objects.hash(selectionComparatorFactory, appliedComparator); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java index 3305d40fda..8989c4416b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.heuristic.selector.common.decorator; +import java.util.Comparator; + import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -23,7 +25,7 @@ */ @Deprecated(forRemoval = true, since = "1.28.0") @FunctionalInterface -public interface SelectionSorterWeightFactory { +public interface SelectionSorterWeightFactory extends ComparatorFactory { /** * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to @@ -32,4 +34,11 @@ public interface SelectionSorterWeightFactory { */ Comparable createSorterWeight(Solution_ solution, T selection); + /** + * Default implementation for enabling interconnection between the two comparator contracts. + */ + @Override + default Comparator createComparator(Solution_ solution) { + return (v1, v2) -> createSorterWeight(solution, v1).compareTo(createSorterWeight(solution, v2)); + } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 86b288a4d0..0e9585bd39 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -19,13 +19,11 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.ValueRangeRecorderId; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByValueSelector; @@ -219,7 +217,7 @@ private static String determineComparatorFactoryPropertyName(EntitySelectorConfi return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } - private static Class + private static Class determineComparatorFactoryClass(EntitySelectorConfig entitySelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(entitySelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { @@ -365,21 +363,15 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach } sorter = EntitySelectorConfig.determineSorter(sorterManner, entityDescriptor); } else if (comparatorClass != null) { - Comparator sorterComparator = + var sorterComparator = instanceCache.newInstance(config, determineComparatorPropertyName(config), comparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - var instance = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + var comparatorFactory = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), comparatorFactoryClass); - ComparatorFactory comparatorFactory; - if (instance instanceof ComparatorFactory factoryInstance) { - comparatorFactory = (ComparatorFactory) factoryInstance; - } else { - comparatorFactory = - new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); - } - sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); + sorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, + SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index b3b7abbc40..4e2c57cce4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -11,13 +11,11 @@ import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.move.Move; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.CachingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector; @@ -155,7 +153,7 @@ private String determineComparatorFactoryPropertyName(MoveSelectorConfig_ moveSe return weightFactoryClass != null ? "sorterWeightFactoryClass" : "comparatorFactoryClass"; } - private Class determineComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { + private Class determineComparatorFactoryClass(MoveSelectorConfig_ moveSelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(moveSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return moveSelectorConfig.getSorterWeightFactoryClass(); @@ -239,21 +237,15 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT var comparatorFactoryClass = determineComparatorFactoryClass(config); var sorterClass = config.getSorterClass(); if (comparatorClass != null) { - Comparator> sorterComparator = + var sorterComparator = ConfigUtils.newInstance(config, determineComparatorPropertyName(config), comparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - var instance = + var comparatorFactory = ConfigUtils.newInstance(config, determineComparatorFactoryPropertyName(config), comparatorFactoryClass); - ComparatorFactory, ?> comparatorFactory; - if (instance instanceof ComparatorFactory factoryInstance) { - comparatorFactory = (ComparatorFactory, ?>) factoryInstance; - } else { - comparatorFactory = - new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory>) instance); - } - sorter = new FactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); + sorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, + SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (sorterClass != null) { sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index eec234d9b3..73aab920fa 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -18,13 +18,11 @@ 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.AbstractSelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactoryAdapter; +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.FactorySelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.CachingValueSelector; @@ -133,9 +131,11 @@ public ValueSelector buildValueSelector(HeuristicConfigPolicy - determineComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { + private static Class determineComparatorFactoryClass(ValueSelectorConfig valueSelectorConfig) { var propertyName = determineComparatorFactoryPropertyName(valueSelectorConfig); if (propertyName.equals("sorterWeightFactoryClass")) { return valueSelectorConfig.getSorterWeightFactoryClass(); @@ -389,16 +388,9 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (comparatorFactoryClass != null) { - var instance = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), + var comparatorFactory = instanceCache.newInstance(config, determineComparatorFactoryPropertyName(config), comparatorFactoryClass); - ComparatorFactory comparatorFactory; - if (instance instanceof ComparatorFactory factoryInstance) { - comparatorFactory = (ComparatorFactory) factoryInstance; - } else { - comparatorFactory = - new ComparatorFactoryAdapter<>((SelectionSorterWeightFactory) instance); - } - sorter = new FactorySelectionSorter<>(comparatorFactory, + sorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); } else if (config.getSorterClass() != null) { sorter = instanceCache.newInstance(config, "sorterClass", config.getSorterClass()); 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 6a99a979d6..def92dd06f 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 @@ -703,7 +703,7 @@ private static List generateBasicVariableConfig @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveOldBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + void solveStrengthBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataDifficultySortableSolution.class, TestdataDifficultySortableEntity.class); solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityDifficultyEasyScoreCalculator.class); @@ -728,7 +728,7 @@ void solveOldBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseC @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveOldBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + void solveStrengthBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataDifficultyFactorySortableSolution.class, TestdataDifficultyFactorySortableEntity.class); @@ -754,7 +754,7 @@ void solveOldBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConf @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveOldBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + void solveStrengthBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataStrengthSortableEntityProvidingSolution.class, @@ -781,7 +781,7 @@ void solveOldBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestCo @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveOldBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + void solveStrengthBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataStrengthFactorySortableEntityProvidingSolution.class, @@ -808,7 +808,7 @@ void solveOldBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfi @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveNewBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + void solveBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataComparatorSortableSolution.class, TestdataComparatorSortableEntity.class); solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityComparatorEasyScoreCalculator.class); @@ -833,7 +833,7 @@ void solveNewBasicVariableQueueComparator(ConstructionHeuristicTestConfig phaseC @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveNewBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + void solveBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataFactorySortableSolution.class, TestdataFactorySortableEntity.class); solverConfig.withEasyScoreCalculatorClass(OneValuePerEntityFactoryEasyScoreCalculator.class); @@ -858,7 +858,7 @@ void solveNewBasicVariableQueueFactory(ConstructionHeuristicTestConfig phaseConf @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveNewBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { + void solveBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataComparatorSortableEntityProvidingSolution.class, @@ -885,7 +885,7 @@ void solveNewBasicVariableEntityRangeQueueComparator(ConstructionHeuristicTestCo @ParameterizedTest @MethodSource("generateBasicVariableConfiguration") - void solveNewBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { + void solveBasicVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig phaseConfig) { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataFactorySortableEntityProvidingSolution.class, diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorterTest.java similarity index 66% rename from core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorterTest.java index 0649810c1a..3ff74694ab 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FactorySelectionSorterTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/ComparatorFactorySelectionSorterTest.java @@ -4,6 +4,7 @@ import static org.mockito.Mockito.mock; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import ai.timefold.solver.core.api.domain.common.ComparatorFactory; @@ -14,14 +15,15 @@ import org.junit.jupiter.api.Test; -class FactorySelectionSorterTest { +class ComparatorFactorySelectionSorterTest { @Test void sortAscending() { - ComparatorFactory weightFactory = (solution, selection) -> Integer - .valueOf(selection.getCode().charAt(0)); - FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( - weightFactory, SelectionSorterOrder.ASCENDING); + ComparatorFactory comparatorFactory = + sol -> Comparator.comparingInt(v -> Integer.valueOf(v.getCode().charAt(0))); + ComparatorFactorySelectionSorter selectionSorter = + new ComparatorFactorySelectionSorter<>( + comparatorFactory, SelectionSorterOrder.ASCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); selectionList.add(new TestdataEntity("C")); @@ -34,10 +36,11 @@ void sortAscending() { @Test void sortDescending() { - ComparatorFactory weightFactory = (solution, selection) -> Integer - .valueOf(selection.getCode().charAt(0)); - FactorySelectionSorter selectionSorter = new FactorySelectionSorter<>( - weightFactory, SelectionSorterOrder.DESCENDING); + ComparatorFactory comparatorFactory = + sol -> Comparator.comparingInt(v -> Integer.valueOf(v.getCode().charAt(0))); + ComparatorFactorySelectionSorter selectionSorter = + new ComparatorFactorySelectionSorter<>( + comparatorFactory, SelectionSorterOrder.DESCENDING); ScoreDirector scoreDirector = mock(ScoreDirector.class); List selectionList = new ArrayList<>(); selectionList.add(new TestdataEntity("C")); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 57758fbdf5..33f80011f7 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -7,7 +7,6 @@ import java.util.Comparator; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -244,17 +243,11 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionComparatorFactory - implements SelectionSorterWeightFactory, - ComparatorFactory { - - @Override - public Integer createSorter(TestdataSolution testdataSolution, TestdataEntity selection) { - return 0; - } + implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(TestdataSolution solution, TestdataEntity selection) { - return createSorter(solution, selection); + return 0; } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index d0b8e7e849..0a06aafc71 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -14,7 +14,6 @@ import java.util.List; import java.util.function.Consumer; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; 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.common.decorator.SelectionSorterOrder; @@ -198,24 +197,18 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPo } } - public static class TestCodeAssertableComparatorFactory - implements SelectionSorterWeightFactory, ComparatorFactory { + public static class TestCodeAssertableComparatorFactory implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(Object o, CodeAssertable selection) { return selection.getCode(); } - - @Override - public String createSorter(Object o, CodeAssertable selection) { - return selection.getCode(); - } } public static class TestCodeAssertableComparator implements Comparator { @Override - public int compare(CodeAssertable o1, CodeAssertable o2) { - return o1.getCode().compareTo(o2.getCode()); + public int compare(CodeAssertable v1, CodeAssertable v2) { + return v1.getCode().compareTo(v2.getCode()); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index d8d804639c..65c4cdcd12 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -11,7 +11,6 @@ import java.util.Iterator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -352,16 +351,11 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } public static class DummySelectionComparatorFactory - implements SelectionSorterWeightFactory, - ComparatorFactory { - @Override - public Integer createSorter(TestdataSolution testdataSolution, TestdataValue selection) { - return 0; - } + implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(TestdataSolution solution, TestdataValue selection) { - return createSorter(solution, selection); + return 0; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java index d04ab9a1e5..e674b6b488 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java @@ -1,21 +1,13 @@ package ai.timefold.solver.core.testdomain.common; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; -public class DummyValueFactory - implements ComparatorFactory, - SelectionSorterWeightFactory { - - @Override - public Integer createSorter(TestdataListFactorySortableSolution solution, TestdataValue selection) { - return 0; - } +public class DummyValueFactory implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, TestdataValue selection) { - return createSorter(solution, selection); + return 0; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java index 1a8ca4b28c..5ca230331c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java @@ -1,18 +1,12 @@ package ai.timefold.solver.core.testdomain.common; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; public class TestSortableFactory - implements SelectionSorterWeightFactory, - ComparatorFactory { - @Override - public Comparable createSorterWeight(Object o, TestSortableObject selection) { - return selection; - } + implements SelectionSorterWeightFactory { @Override - public TestSortableObject createSorter(Object o, TestSortableObject selection) { + public Comparable createSorterWeight(Object o, TestSortableObject selection) { return selection; } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java index 27df5adc6c..e4bff2ea6c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java @@ -1,19 +1,12 @@ package ai.timefold.solver.core.testdomain.common; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.testdomain.TestdataObject; -public class TestdataObjectSortableDescendingFactory - implements ComparatorFactory, SelectionSorterWeightFactory { +public class TestdataObjectSortableDescendingFactory implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(Object solution, TestdataObject selection) { - return createSorter(solution, selection); - } - - @Override - public Integer createSorter(Object solution, TestdataObject selection) { // Descending order return -extractCode(selection.getCode()); } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java index 4e26dc9642..c76995fd7f 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/difficultyweight/TestdataDifficultyFactory.java @@ -1,29 +1,13 @@ package ai.timefold.solver.core.testdomain.difficultyweight; -import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; -public class TestdataDifficultyFactory implements - ComparatorFactory, - SelectionSorterWeightFactory { - - @Override - public TestdataDifficultyWeightComparable createSorter(TestdataDifficultyWeightSolution solution, - TestdataDifficultyWeightEntity entity) { - return new TestdataDifficultyWeightComparable(); - } +public class TestdataDifficultyFactory + implements SelectionSorterWeightFactory { @Override public Comparable createSorterWeight(TestdataDifficultyWeightSolution testdataDifficultyWeightSolution, TestdataDifficultyWeightEntity selection) { - return createSorter(testdataDifficultyWeightSolution, selection); - } - - public static class TestdataDifficultyWeightComparable implements Comparable { - - @Override - public int compareTo(TestdataDifficultyWeightComparable other) { - return 0; - } + return 0; } } diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 2925fa6e02..349c47b0b1 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -1609,7 +1609,7 @@ If you need the entire solution to sort a ``Selector``, use a `ComparatorFactory ---- public interface ComparatorFactory { - Comparable createSorter(Solution_ solution, T selection); + Comparator createComparator(Solution_ solution, T selection); } ---- diff --git a/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java b/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java index cb8fdabd18..c97daf5da3 100644 --- a/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java +++ b/migration/src/main/java/ai/timefold/solver/migration/v8/SortingMigrationRecipe.java @@ -27,7 +27,7 @@ public List getRecipeList() { // Update ComparatorFactory new ChangeMethodName( "ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory createSorterWeight(..)", - "createSorter", true, null), + "createComparator", true, null), new ChangeType("ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory", "ai.timefold.solver.core.api.domain.common.ComparatorFactory", true), // Update PlanningVariable sorting fields diff --git a/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java index 34df5ca737..9944e8c529 100644 --- a/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java +++ b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java @@ -104,7 +104,8 @@ private static String adjustBefore(String content) { import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner; @PlanningEntity(difficultyWeightFactoryClass = PlanningEntity.NullDifficultyWeightFactory.class, difficultyComparatorClass = PlanningEntity.NullDifficultyComparator.class) - public class Test implements SelectionSorterWeightFactory { + public class Test { + @PlanningVariable(strengthComparatorClass = PlanningVariable.NullStrengthComparator.class) private Object value; @PlanningVariable(strengthWeightFactoryClass = PlanningVariable.NullStrengthWeightFactory.class) @@ -112,8 +113,6 @@ public class Test implements SelectionSorterWeightFactory { public void validate(ChangeMoveSelectorConfig changeMoveConfig, ListSwapMoveSelectorConfig swapMoveConfig, EntitySelectorConfig entityConfig, ValueSelectorConfig valueConfig) { %8s%s } - @Override - public Comparable createSorterWeight(Object o, Object o2) { return null; } }""" .formatted("", content); } @@ -135,7 +134,7 @@ private static String adjustAfter(String content) { import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner; @PlanningEntity(comparatorFactoryClass = PlanningEntity.NullComparatorFactory.class, comparatorClass = PlanningEntity.NullComparator.class) - public class Test implements ComparatorFactory { + public class Test { @PlanningVariable(comparatorClass = NullComparator.class) private Object value; @PlanningVariable(comparatorFactoryClass = NullComparatorFactory.class) @@ -143,8 +142,6 @@ public class Test implements ComparatorFactory { public void validate(ChangeMoveSelectorConfig changeMoveConfig, ListSwapMoveSelectorConfig swapMoveConfig, EntitySelectorConfig entityConfig, ValueSelectorConfig valueConfig) { %8s%s } - @Override - public Comparable createSorter(Object o, Object o2) { return null; } }""" .formatted("", content); } From 3aea98c5d0502147bce1d82d9db92bed8f9daeec Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 23 Oct 2025 14:16:26 -0300 Subject: [PATCH 33/33] chore: address comments --- .../api/domain/entity/PlanningEntity.java | 2 +- .../api/domain/variable/PlanningVariable.java | 2 +- .../entity/descriptor/EntityDescriptor.java | 3 +- .../descriptor/GenuineVariableDescriptor.java | 3 +- .../move/MoveSelectorFactoryTest.java | 92 +++++++++---------- .../v8/SortingMigrationRecipeTest.java | 3 +- 6 files changed, 49 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java index 383ac1112a..f041b0b872 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java @@ -122,7 +122,7 @@ interface NullDifficultyComparator extends NullComparator { } /** - * The {@link ComparatorFactory} alternative for {@link #difficultyComparatorClass()}. + * The {@link SelectionSorterWeightFactory} alternative for {@link #difficultyComparatorClass()}. *

* Do not use together with {@link #difficultyComparatorClass()}. * diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index fa0fc8227a..7f9967fd22 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -131,7 +131,7 @@ interface NullStrengthComparator extends NullComparator { } /** - * The {@link ComparatorFactory} alternative for {@link #strengthComparatorClass()}. + * The {@link SelectionSorterWeightFactory} alternative for {@link #strengthComparatorClass()}. *

* Do not use together with {@link #strengthComparatorClass()}. * 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 91476399ea..48df189078 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 @@ -320,8 +320,7 @@ private void processSorting(PlanningEntity entityAnnotation) { if (selectedComparatorClass != null) { var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorPropertyName, selectedComparatorClass); descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); - } - if (selectedComparatorFactoryClass != null) { + } else if (selectedComparatorFactoryClass != null) { var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass); descendingSorter = new ComparatorFactorySelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 0e02da7e89..a749abf63f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -177,8 +177,7 @@ protected void processSorting(String comparatorPropertyName, Class(comparator, SelectionSorterOrder.DESCENDING); - } - if (comparatorFactoryClass != null) { + } else if (comparatorFactoryClass != null) { ComparatorFactory comparatorFactory = newInstance(this::toString, comparatorFactoryPropertyName, comparatorFactoryClass); ascendingSorter = new ComparatorFactorySelectionSorter<>(comparatorFactory, SelectionSorterOrder.ASCENDING); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index 26234fe5e6..752d5badd6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -202,58 +202,54 @@ void applySorting_withoutAnySortingClass() { @Test void applySorting_withSorterComparatorClass() { - // Old setting - { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setSorterComparatorClass(DummyComparator.class); - - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = - moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); - assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); - } - // New setting - { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setComparatorClass(DummyComparator.class); - - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = - moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); - assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); - } + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterComparatorClass(DummyComparator.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } + + @Test + void applySorting_withComparatorClass() { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setComparatorClass(DummyComparator.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); + } + + @Test + void applySorting_withWeightFactoryClass() { + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setSorterWeightFactoryClass(DummyValueFactory.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); } @Test void applySorting_withComparatorFactoryClass() { - // Old setting - { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setSorterWeightFactoryClass(DummyValueFactory.class); - - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = - moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); - assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); - } - // New setting - { - final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); - DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); - moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); - - DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); - MoveSelector sortingMoveSelector = - moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); - assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); - } + final MoveSelector baseMoveSelector = SelectorTestUtils.mockMoveSelector(); + DummyMoveSelectorConfig moveSelectorConfig = new DummyMoveSelectorConfig(); + moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); + moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); + + DummyMoveSelectorFactory moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); + MoveSelector sortingMoveSelector = + moveSelectorFactory.applySorting(SelectionCacheType.PHASE, SelectionOrder.SORTED, baseMoveSelector); + assertThat(sortingMoveSelector).isExactlyInstanceOf(SortingMoveSelector.class); } @Test diff --git a/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java index 9944e8c529..7ec1a86be8 100644 --- a/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java +++ b/migration/src/test/java/ai/timefold/solver/migration/v8/SortingMigrationRecipeTest.java @@ -20,7 +20,7 @@ public void defaults(RecipeSpec spec) { } @Test - void constraintMethods() { + void migrate() { runTest( """ changeMoveConfig.withSorterWeightFactoryClass(SelectionSorterWeightFactory.class); @@ -105,7 +105,6 @@ private static String adjustBefore(String content) { @PlanningEntity(difficultyWeightFactoryClass = PlanningEntity.NullDifficultyWeightFactory.class, difficultyComparatorClass = PlanningEntity.NullDifficultyComparator.class) public class Test { - @PlanningVariable(strengthComparatorClass = PlanningVariable.NullStrengthComparator.class) private Object value; @PlanningVariable(strengthWeightFactoryClass = PlanningVariable.NullStrengthWeightFactory.class)