Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6a0f429
chore: update factory class for basic variables
zepfred Sep 24, 2025
b77045f
chore: update factory class for planning entities and planning values
zepfred Sep 24, 2025
a222d19
chore: update factory class for moves
zepfred Sep 24, 2025
f91745a
feat: enable sorting for CH and list variable
zepfred Sep 25, 2025
e2345dd
chore: improve tests
zepfred Sep 25, 2025
64eead1
chore: add new tests
zepfred Sep 25, 2025
18e5207
chore: formatting
zepfred Sep 25, 2025
051bcc8
feat: sorting with entity-range
zepfred Sep 26, 2025
9ed8ead
chore: migration recipe
zepfred Sep 26, 2025
59ad835
chore: address comments
zepfred Sep 26, 2025
a18f31b
feat: adjust list variable sorting fields
zepfred Oct 10, 2025
c3c6208
chore: renaming
zepfred Oct 10, 2025
6e95e67
chore: add CH tests
zepfred Oct 13, 2025
07dc956
chore: add new basic var sorting setting
zepfred Oct 13, 2025
669ba53
chore: update entity sorting settings
zepfred Oct 14, 2025
de069a6
chore: address sonar
zepfred Oct 14, 2025
7a0469c
chore: add new factory class setting to entity config
zepfred Oct 20, 2025
db55b0d
chore: add new factory class setting to value config
zepfred Oct 20, 2025
3bd71b9
chore: add new factory class setting to move config
zepfred Oct 20, 2025
97a23a2
docs: update sorting documentation
zepfred Oct 20, 2025
50c1664
chore: migration recipes
zepfred Oct 21, 2025
477f4e7
chore: address comments
zepfred Oct 21, 2025
01b5968
docs: minor changes
zepfred Oct 21, 2025
f1c8911
chore: address comments
zepfred Oct 21, 2025
9c0b87a
chore: address comments
zepfred Oct 22, 2025
38c183c
chore: address comments
zepfred Oct 22, 2025
b18c9e4
chore: address comments
zepfred Oct 22, 2025
0c65111
chore: address comments
zepfred Oct 22, 2025
e09cb52
chore: address comments
zepfred Oct 22, 2025
c8ffe30
chore: address comments
zepfred Oct 22, 2025
78d417c
chore: address sonar
zepfred Oct 22, 2025
bcb4e08
chore: address comments
zepfred Oct 23, 2025
3aea98c
chore: address comments
zepfred Oct 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions benchmark/src/main/resources/benchmark.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -890,9 +890,15 @@
<xs:element minOccurs="0" name="sorterComparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterWeightFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterOrder" type="tns:selectionSorterOrder"/>


Expand Down Expand Up @@ -1082,9 +1088,15 @@
<xs:element minOccurs="0" name="sorterComparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterWeightFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterOrder" type="tns:selectionSorterOrder"/>


Expand Down Expand Up @@ -1229,9 +1241,15 @@
<xs:element minOccurs="0" name="sorterComparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterWeightFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="comparatorFactoryClass" type="xs:string"/>


<xs:element minOccurs="0" name="sorterOrder" type="tns:selectionSorterOrder"/>


Expand Down Expand Up @@ -2874,6 +2892,12 @@


<xs:enumeration value="DECREASING_DIFFICULTY_IF_AVAILABLE"/>


<xs:enumeration value="DESCENDING"/>


<xs:enumeration value="DESCENDING_IF_AVAILABLE"/>


</xs:restriction>
Expand Down Expand Up @@ -2901,6 +2925,18 @@


<xs:enumeration value="DECREASING_STRENGTH_IF_AVAILABLE"/>


<xs:enumeration value="ASCENDING"/>


<xs:enumeration value="ASCENDING_IF_AVAILABLE"/>


<xs:enumeration value="DESCENDING"/>


<xs:enumeration value="DESCENDING_IF_AVAILABLE"/>


</xs:restriction>
Expand Down
102 changes: 102 additions & 0 deletions core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,108 @@
"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<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory> ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::getSorterWeightFactoryClass()",
"new": "method java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory> 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<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory>===)",
"new": "parameter void ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig::setSorterWeightFactoryClass(===java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory>===)",
"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",
"old": "method java.lang.Class<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory> ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>::getSorterWeightFactoryClass()",
"new": "method java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory> ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>::getSorterWeightFactoryClass()",
"justification": "New comparator factory class"
},
{
"ignore": true,
"code": "java.method.parameterTypeParameterChanged",
"old": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>::setSorterWeightFactoryClass(===java.lang.Class<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory>===)",
"new": "parameter void ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>::setSorterWeightFactoryClass(===java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory>===)",
"parameterIndex": "0",
"justification": "New comparator factory class"
},
{
"ignore": true,
"code": "java.annotation.attributeValueChanged",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>",
"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",
"old": "method java.lang.Class<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory> ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::getSorterWeightFactoryClass()",
"new": "method java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory> 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<? extends ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory>===)",
"new": "parameter void ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig::setSorterWeightFactoryClass(===java.lang.Class<? extends ai.timefold.solver.core.api.domain.common.ComparatorFactory>===)",
"parameterIndex": "0",
"justification": "New comparator factory class"
},
{
"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\", \"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<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_ extends ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig<Config_>>",
"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"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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;
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;

import org.jspecify.annotations.NullMarked;

/**
* 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 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},
* specifies how the data will be sorted.
* Additionally,
* the property {@code constructionHeuristicType} from {@link ConstructionHeuristicPhaseConfig} can also configure how entities
* and values are sorted.
* <p>
* Implementations are expected to be stateless.
* The solver may choose to reuse instances.
*
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
* @param <T> the selection type
*
* @see ValueSelectorConfig
* @see EntitySelectorConfig
* @see ConstructionHeuristicPhaseConfig
*/
@NullMarked
@FunctionalInterface
public interface ComparatorFactory<Solution_, T> {

/**
* @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to
* @return never null
*/
Comparator<T> createComparator(Solution_ solution);

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.lang.annotation.Target;
import java.util.Comparator;

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;
Expand Down Expand Up @@ -34,6 +35,40 @@
@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.
* <p>
* 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)
* <p>
* Do not use together with {@link #comparatorFactoryClass()}.
*
* @return {@link PlanningVariable.NullComparator} when it is null (workaround for annotation limitation)
* @see #comparatorFactoryClass()
*/
Class<? extends Comparator> comparatorClass() default NullComparator.class;

interface NullComparator<T> extends Comparator<T> {
}

/**
* The {@link ComparatorFactory} alternative for {@link #comparatorClass()}.
* <p>
* Differs from {@link #comparatorClass()}
* because it allows accessing the current solution when creating the comparator.
* <p>
* Do not use together with {@link #comparatorClass()}.
*
* @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation)
* @see #comparatorClass()
*/
Class<? extends ComparatorFactory> comparatorFactoryClass() default NullComparatorFactory.class;

interface NullComparatorFactory<Solution_, T> extends ComparatorFactory<Solution_, T> {
}

/**
* 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).
Expand All @@ -50,7 +85,7 @@

/**
* Workaround for annotation limitation in {@link #pinningFilter()}.
*
*
* @deprecated Prefer using {@link PlanningPin}.
*/
@Deprecated(forRemoval = true, since = "1.23.0")
Expand All @@ -69,27 +104,45 @@ interface NullPinningFilter extends PinningFilter {
* <p>
* 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<? extends Comparator> difficultyComparatorClass() default NullDifficultyComparator.class;

/** Workaround for annotation limitation in {@link #difficultyComparatorClass()}. */
interface NullDifficultyComparator extends Comparator {
/**
* Workaround for annotation limitation in {@link #difficultyComparatorClass()}.
*
* @deprecated Deprecated in favor of {@link NullComparator}.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
interface NullDifficultyComparator<T> extends NullComparator<T> {
}

/**
* The {@link SelectionSorterWeightFactory} alternative for {@link #difficultyComparatorClass()}.
* <p>
* 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()
*/
@Deprecated(forRemoval = true, since = "1.28.0")
Class<? extends SelectionSorterWeightFactory> difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class;

/** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */
interface NullDifficultyWeightFactory extends SelectionSorterWeightFactory {
/**
* Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}.
*
* @deprecated Deprecated in favor of {@link NullComparatorFactory}.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
interface NullDifficultyWeightFactory<Solution_, T>
extends SelectionSorterWeightFactory<Solution_, T>,
NullComparatorFactory<Solution_, T> {
}

}
Loading
Loading