From 6a0f4299a592674686d6b2a74be12bcea68af92f Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 24 Sep 2025 09:40:17 -0300 Subject: [PATCH 01/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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) From af346d9acba9ee7846efae7b7d8ef6d74cd72adc Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 28 Oct 2025 09:41:53 -0300 Subject: [PATCH 34/36] chore: increase coverage --- .../selector/entity/EntitySelectorFactoryTest.java | 14 ++++++++++++++ .../selector/value/ValueSelectorFactoryTest.java | 14 ++++++++++++++ 2 files changed, 28 insertions(+) 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 33f80011f7..f6f403ca98 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 @@ -150,6 +150,13 @@ void applySorting_withSorterComparatorClass() { applySorting(entitySelectorConfig); } + @Test + void applySorting_withComparatorClass() { + EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() + .withComparatorClass(DummyEntityComparator.class); + applySorting(entitySelectorConfig); + } + @Test void applySorting_withSorterWeightFactoryClass() { EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() @@ -157,6 +164,13 @@ void applySorting_withSorterWeightFactoryClass() { applySorting(entitySelectorConfig); } + @Test + void applySorting_withComparatorFactoryClass() { + EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() + .withComparatorFactoryClass(DummySelectionComparatorFactory.class); + applySorting(entitySelectorConfig); + } + private void applySorting(EntitySelectorConfig entitySelectorConfig) { EntitySelectorFactory entitySelectorFactory = EntitySelectorFactory.create(entitySelectorConfig); 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 65c4cdcd12..0b8c2ddfbb 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 @@ -215,6 +215,13 @@ void applySorting_withSorterComparatorClass() { applySorting(valueSelectorConfig); } + @Test + void applySorting_withComparatorClass() { + ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() + .withComparatorClass(DummyValueComparator.class); + applySorting(valueSelectorConfig); + } + @Test void applySorting_withSorterWeightFactoryClass() { ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() @@ -222,6 +229,13 @@ void applySorting_withSorterWeightFactoryClass() { applySorting(valueSelectorConfig); } + @Test + void applySorting_withComparatorFactoryClass() { + ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() + .withComparatorFactoryClass(DummySelectionComparatorFactory.class); + applySorting(valueSelectorConfig); + } + private void applySorting(ValueSelectorConfig valueSelectorConfig) { ValueSelectorFactory valueSelectorFactory = ValueSelectorFactory.create(valueSelectorConfig); From 92ad6062262ac772a81632836e28e361defc73fd Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 28 Oct 2025 09:42:12 -0300 Subject: [PATCH 35/36] chore: fix migration --- .../upgrade-to-latest-version.adoc | 11 +++++++++++ .../migration/v8/SortingMigrationRecipe.java | 6 ------ .../v8/SortingMigrationRecipeTest.java | 19 +++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-to-latest-version.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-to-latest-version.adoc index 587e864eb5..de79033a06 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-to-latest-version.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-to-latest-version.adoc @@ -59,6 +59,17 @@ Every upgrade note indicates how likely your code will be affected by that chang The upgrade recipe often lists the changes as they apply to Java code. We kindly ask Kotlin users to translate the changes accordingly. +=== Upgrade from 1.27.0 to 1.28.0 + +.icon:info-circle[role=yellow] `SelectionSorterWeightFactory` deprecated for removal +[%collapsible%open] +==== +The `SelectionSorterWeightFactory` sorter class has been deprecated in favor of `ComparatorFactory`. +The sorting settings now utilize a `Comparator` class rather than a `Comparable` class, +ensuring all related settings are consistent and simpler. +The old class remains valid and can be easily converted to use the `ComparatorFactory` instead. +==== + === Upgrade from 1.22.0 to 1.23.0 .icon:info-circle[role=yellow] `@PlanningEntity` `pinningFilter` deprecated for removal 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 c97daf5da3..d697e8aacd 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 @@ -24,12 +24,6 @@ public String getDescription() { @Override public List getRecipeList() { return List.of( - // Update ComparatorFactory - new ChangeMethodName( - "ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory createSorterWeight(..)", - "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 new ChangeAnnotationAttributeName("ai.timefold.solver.core.api.domain.variable.PlanningVariable", "strengthComparatorClass", "comparatorClass"), 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 7ec1a86be8..86e097c457 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 @@ -54,29 +54,29 @@ void migrate() { valueConfig.setSorterManner(ValueSorterManner.DECREASING_STRENGTH); valueConfig.setSorterManner(ValueSorterManner.DECREASING_STRENGTH_IF_AVAILABLE);""", """ - changeMoveConfig.withComparatorFactoryClass(ComparatorFactory.class); + changeMoveConfig.withComparatorFactoryClass(SelectionSorterWeightFactory.class); changeMoveConfig.withComparatorClass(Comparator.class); - changeMoveConfig.setComparatorFactoryClass(ComparatorFactory.class); + changeMoveConfig.setComparatorFactoryClass(SelectionSorterWeightFactory.class); changeMoveConfig.setComparatorClass(Comparator.class); changeMoveConfig.getComparatorFactoryClass(); changeMoveConfig.getComparatorClass(); - swapMoveConfig.withComparatorFactoryClass(ComparatorFactory.class); + swapMoveConfig.withComparatorFactoryClass(SelectionSorterWeightFactory.class); swapMoveConfig.withComparatorClass(Comparator.class); - swapMoveConfig.setComparatorFactoryClass(ComparatorFactory.class); + swapMoveConfig.setComparatorFactoryClass(SelectionSorterWeightFactory.class); swapMoveConfig.setComparatorClass(Comparator.class); swapMoveConfig.getComparatorFactoryClass(); swapMoveConfig.getComparatorClass(); - entityConfig.withComparatorFactoryClass(ComparatorFactory.class); + entityConfig.withComparatorFactoryClass(SelectionSorterWeightFactory.class); entityConfig.withComparatorClass(Comparator.class); - entityConfig.setComparatorFactoryClass(ComparatorFactory.class); + entityConfig.setComparatorFactoryClass(SelectionSorterWeightFactory.class); entityConfig.setComparatorClass(Comparator.class); entityConfig.getComparatorFactoryClass(); entityConfig.getComparatorClass(); entityConfig.setSorterManner(EntitySorterManner.DESCENDING_IF_AVAILABLE); entityConfig.setSorterManner(EntitySorterManner.DESCENDING); - valueConfig.withComparatorFactoryClass(ComparatorFactory.class); + valueConfig.withComparatorFactoryClass(SelectionSorterWeightFactory.class); valueConfig.withComparatorClass(Comparator.class); - valueConfig.setComparatorFactoryClass(ComparatorFactory.class); + valueConfig.setComparatorFactoryClass(SelectionSorterWeightFactory.class); valueConfig.setComparatorClass(Comparator.class); valueConfig.getComparatorFactoryClass(); valueConfig.getComparatorClass(); @@ -119,8 +119,7 @@ public void validate(ChangeMoveSelectorConfig changeMoveConfig, ListSwapMoveSele 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.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.api.domain.variable.PlanningVariable.NullComparator; From 8eb1cd284994c68d022302bdf808f2aeb39457f2 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 28 Oct 2025 09:42:36 -0300 Subject: [PATCH 36/36] chore: minor doc improvement --- .../optimization-algorithms/construction-heuristics.adoc | 3 +-- .../modules/ROOT/pages/optimization-algorithms/overview.adoc | 4 ++-- .../using-timefold-solver/modeling-planning-problems.adoc | 2 +- 3 files changed, 4 insertions(+), 5 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 99e6497b4a..469471a4a3 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/construction-heuristics.adoc @@ -569,8 +569,7 @@ Advanced configuration for a single entity class with a single variable: SORTED DESCENDING - - + diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 349c47b0b1..103e93bbdb 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 { - Comparator createComparator(Solution_ solution, T selection); + Comparator createComparator(Solution_ solution); } ---- @@ -1621,7 +1621,7 @@ You'll also need to configure it (unless it's annotated on the domain model and PHASE SORTED - ...MySorterComparatorFactory + ...MyComparatorFactory 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 6cfb90709b..c7f3004f81 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 @@ -420,7 +420,7 @@ See xref:optimization-algorithms/overview.adoc#sortedSelection[sorted selection] [IMPORTANT] ==== -Entities should be sorted in ascending order: 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 in descending order, they just reverse the ordering.