Skip to content

Commit bcfdf5d

Browse files
committed
chore: add new basic var sorting setting
1 parent 39eb75c commit bcfdf5d

File tree

80 files changed

+1992
-1007
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1992
-1007
lines changed

core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.lang.annotation.Target;
99
import java.util.Comparator;
1010

11+
import ai.timefold.solver.core.api.domain.common.SorterFactory;
1112
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
1213
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
1314
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
@@ -78,11 +79,29 @@
7879
* <p>
7980
* Do not use together with {@link #strengthWeightFactoryClass()}.
8081
*
82+
* @deprecated Deprecated in favor of {@link #comparatorClass()}.
83+
*
8184
* @return {@link NullStrengthComparator} when it is null (workaround for annotation limitation)
8285
* @see #strengthWeightFactoryClass()
8386
*/
87+
@Deprecated(forRemoval = true, since = "1.28.0")
8488
Class<? extends Comparator> strengthComparatorClass() default NullStrengthComparator.class;
8589

90+
/**
91+
* Allows sorting a collection of planning values for this variable.
92+
* Some algorithms perform better when the values are sorted based on specific metrics.
93+
* <p>
94+
* The {@link Comparator} should sort the data in ascending order.
95+
* For example, prioritize three visits by sorting them based on their importance:
96+
* Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY)
97+
* <p>
98+
* Do not use together with {@link #comparatorFactoryClass()}.
99+
*
100+
* @return {@link NullComparator} when it is null (workaround for annotation limitation)
101+
* @see #comparatorFactoryClass()
102+
*/
103+
Class<? extends Comparator> comparatorClass() default NullComparator.class;
104+
86105
/** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */
87106
interface NullStrengthComparator extends NullComparator {
88107
}
@@ -95,11 +114,24 @@ interface NullComparator extends Comparator {
95114
* <p>
96115
* Do not use together with {@link #strengthComparatorClass()}.
97116
*
117+
* @deprecated Deprecated in favor of {@link #comparatorFactoryClass()}.
118+
*
98119
* @return {@link NullStrengthWeightFactory} when it is null (workaround for annotation limitation)
99120
* @see #strengthComparatorClass()
100121
*/
122+
@Deprecated(forRemoval = true, since = "1.28.0")
101123
Class<? extends SelectionSorterWeightFactory> strengthWeightFactoryClass() default NullStrengthWeightFactory.class;
102124

125+
/**
126+
* The {@link SorterFactory} alternative for {@link #comparatorClass()}.
127+
* <p>
128+
* Do not use together with {@link #comparatorClass()}.
129+
130+
* @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation)
131+
* @see #comparatorClass()
132+
*/
133+
Class<? extends SorterFactory> comparatorFactoryClass() default NullComparatorFactory.class;
134+
103135
/** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */
104136
interface NullStrengthWeightFactory extends NullComparatorFactory {
105137
}

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ai.timefold.solver.core.impl.domain.variable.descriptor;
22

3+
import ai.timefold.solver.core.api.domain.common.SorterFactory;
34
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
45
import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType;
56
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
@@ -8,6 +9,8 @@
89
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
910
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.MovableChainedTrailingValueFilter;
1011

12+
import java.util.Comparator;
13+
1114
public final class BasicVariableDescriptor<Solution_> extends GenuineVariableDescriptor<Solution_> {
1215

1316
private SelectionFilter<Solution_, Object> movableChainedTrailingValueFilter;
@@ -41,8 +44,60 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) {
4144
processAllowsUnassigned(planningVariableAnnotation);
4245
processChained(planningVariableAnnotation);
4346
processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs());
44-
processSorting("strengthComparatorClass", planningVariableAnnotation.strengthComparatorClass(),
45-
"strengthWeightFactoryClass", planningVariableAnnotation.strengthWeightFactoryClass());
47+
var sortingProperties = assertSortingProperties(planningVariableAnnotation);
48+
processSorting(sortingProperties.comparatorPropertyName(), sortingProperties.comparatorClass(),
49+
sortingProperties.comparatorFactoryPropertyName(), sortingProperties.comparatorFactoryClass());
50+
}
51+
52+
private SortingProperties assertSortingProperties(PlanningVariable planningVariableAnnotation) {
53+
// Comparator property
54+
var strengthComparatorClass = planningVariableAnnotation.strengthComparatorClass();
55+
var comparatorClass = planningVariableAnnotation.comparatorClass();
56+
if (strengthComparatorClass != null
57+
&& PlanningVariable.NullComparator.class.isAssignableFrom(strengthComparatorClass)) {
58+
strengthComparatorClass = null;
59+
}
60+
if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) {
61+
comparatorClass = null;
62+
}
63+
if (strengthComparatorClass != null && comparatorClass != null) {
64+
throw new IllegalStateException(
65+
"The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(
66+
entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthComparatorClass",
67+
strengthComparatorClass.getName(), "comparatorClass", comparatorClass.getName()));
68+
}
69+
// Comparator factory property
70+
var strengthWeightFactoryClass = planningVariableAnnotation.strengthWeightFactoryClass();
71+
var comparatorFactoryClass = planningVariableAnnotation.comparatorFactoryClass();
72+
if (strengthWeightFactoryClass != null
73+
&& PlanningVariable.NullComparatorFactory.class.isAssignableFrom(strengthWeightFactoryClass)) {
74+
strengthWeightFactoryClass = null;
75+
}
76+
if (comparatorFactoryClass != null
77+
&& PlanningVariable.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) {
78+
comparatorFactoryClass = null;
79+
}
80+
if (strengthWeightFactoryClass != null && comparatorFactoryClass != null) {
81+
throw new IllegalStateException(
82+
"The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(
83+
entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthWeightFactoryClass",
84+
strengthWeightFactoryClass.getName(), "comparatorFactoryClass", comparatorFactoryClass.getName()));
85+
}
86+
// Final properties
87+
var comparatorPropertyName = "comparatorClass";
88+
var comparatorPropertyClass = comparatorClass;
89+
var factoryPropertyName = "comparatorFactoryClass";
90+
var factoryPropertyClass = comparatorFactoryClass;
91+
if (strengthComparatorClass != null) {
92+
comparatorPropertyName = "strengthComparatorClass";
93+
comparatorPropertyClass = strengthComparatorClass;
94+
}
95+
if (strengthWeightFactoryClass != null) {
96+
factoryPropertyName = "strengthWeightFactoryClass";
97+
factoryPropertyClass = strengthWeightFactoryClass;
98+
}
99+
return new SortingProperties(comparatorPropertyName, comparatorPropertyClass, factoryPropertyName,
100+
factoryPropertyClass);
46101
}
47102

48103
private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) {
@@ -130,4 +185,9 @@ public SelectionFilter<Solution_, Object> getMovableChainedTrailingValueFilter()
130185
return movableChainedTrailingValueFilter;
131186
}
132187

188+
private record SortingProperties(String comparatorPropertyName, Class<? extends Comparator> comparatorClass,
189+
String comparatorFactoryPropertyName, Class<? extends SorterFactory> comparatorFactoryClass) {
190+
191+
}
192+
133193
}

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
2222
* @param <T> the selection type
2323
*/
24-
@Deprecated(forRemoval = true, since = "1.27.0")
24+
@Deprecated(forRemoval = true, since = "1.28.0")
2525
public interface SelectionSorterWeightFactory<Solution_, T> extends SorterFactory<Solution_, T> {
2626

2727
Comparable createSorterWeight(Solution_ solution, T selection);

0 commit comments

Comments
 (0)