@@ -122,10 +122,19 @@ public ValueSelector<Solution_> buildValueSelector(HeuristicConfigPolicy<Solutio
122122
123123 // baseValueSelector and lower should be SelectionOrder.ORIGINAL if they are going to get cached completely
124124 var randomSelection = determineBaseRandomSelection (variableDescriptor , resolvedCacheType , resolvedSelectionOrder );
125- var valueSelector =
126- buildBaseValueSelector (variableDescriptor , SelectionCacheType .max (minimumCacheType , resolvedCacheType ),
127- randomSelection );
128125 var instanceCache = configPolicy .getClassInstanceCache ();
126+ /*
127+ * When a filtering value range or nearby are used,
128+ * we opt to sort the data using a sorting node selector instead of sorting at the value range level.
129+ * This choice is required
130+ * because the FilteringValueRangeSelector or the related Nearby selector does not respect the sort order
131+ * provided by the child selectors.
132+ */
133+ var canSortAtValueRangeLevel = entityValueRangeRecorderId == null && nearbySelectionConfig == null ;
134+ var sorter =
135+ canSortAtValueRangeLevel ? determineSorter (variableDescriptor , resolvedSelectionOrder , instanceCache ) : null ;
136+ var valueSelector = buildBaseValueSelector (variableDescriptor , sorter ,
137+ SelectionCacheType .max (minimumCacheType , resolvedCacheType ), randomSelection );
129138 if (nearbySelectionConfig != null ) {
130139 // TODO Static filtering (such as movableEntitySelectionFilter) should affect nearbySelection too
131140 valueSelector = applyNearbySelection (configPolicy , entityDescriptor , minimumCacheType ,
@@ -141,7 +150,9 @@ public ValueSelector<Solution_> buildValueSelector(HeuristicConfigPolicy<Solutio
141150 }
142151 valueSelector = applyFiltering (valueSelector , instanceCache );
143152 valueSelector = applyInitializedChainedValueFilter (configPolicy , variableDescriptor , valueSelector );
144- valueSelector = applySorting (resolvedCacheType , resolvedSelectionOrder , valueSelector , instanceCache );
153+ if (!canSortAtValueRangeLevel ) {
154+ valueSelector = applySorting (resolvedCacheType , resolvedSelectionOrder , valueSelector , instanceCache );
155+ }
145156 valueSelector = applyProbability (resolvedCacheType , resolvedSelectionOrder , valueSelector , instanceCache );
146157 valueSelector = applyShuffling (resolvedCacheType , resolvedSelectionOrder , valueSelector );
147158 valueSelector = applyCaching (resolvedCacheType , resolvedSelectionOrder , valueSelector );
@@ -260,8 +271,45 @@ private static Class<? extends ComparatorFactory> determineComparatorFactoryClas
260271 }
261272 }
262273
274+ private SelectionSorter <Solution_ , Object > determineSorter (GenuineVariableDescriptor <Solution_ > variableDescriptor ,
275+ SelectionOrder resolvedSelectionOrder , ClassInstanceCache instanceCache ) {
276+ SelectionSorter <Solution_ , Object > sorter = null ;
277+ if (resolvedSelectionOrder == ai .timefold .solver .core .config .heuristic .selector .common .SelectionOrder .SORTED ) {
278+ var sorterManner = config .getSorterManner ();
279+ var comparatorClass = determineComparatorClass (config );
280+ var comparatorFactoryClass = determineComparatorFactoryClass (config );
281+ if (sorterManner != null ) {
282+ if (!ValueSelectorConfig .hasSorter (sorterManner , variableDescriptor )) {
283+ return null ;
284+ }
285+ sorter = ValueSelectorConfig .determineSorter (sorterManner , variableDescriptor );
286+ } else if (comparatorClass != null ) {
287+ Comparator <Object > sorterComparator =
288+ instanceCache .newInstance (config , determineComparatorPropertyName (config ), comparatorClass );
289+ sorter = new ComparatorSelectionSorter <>(sorterComparator ,
290+ SelectionSorterOrder .resolve (config .getSorterOrder ()));
291+ } else if (comparatorFactoryClass != null ) {
292+ var comparatorFactory = instanceCache .newInstance (config , determineComparatorFactoryPropertyName (config ),
293+ comparatorFactoryClass );
294+ sorter = new ComparatorFactorySelectionSorter <>(comparatorFactory ,
295+ SelectionSorterOrder .resolve (config .getSorterOrder ()));
296+ } else if (config .getSorterClass () != null ) {
297+ sorter = instanceCache .newInstance (config , "sorterClass" , config .getSorterClass ());
298+ } else {
299+ throw new IllegalArgumentException ("""
300+ The valueSelectorConfig (%s) with resolvedSelectionOrder (%s) needs \
301+ a sorterManner (%s) or a %s (%s) or a %s (%s) \
302+ or a sorterClass (%s)."""
303+ .formatted (config , resolvedSelectionOrder , sorterManner , determineComparatorPropertyName (config ),
304+ comparatorClass , determineComparatorFactoryPropertyName (config ), comparatorFactoryClass ,
305+ config .getSorterClass ()));
306+ }
307+ }
308+ return sorter ;
309+ }
310+
263311 private ValueSelector <Solution_ > buildBaseValueSelector (GenuineVariableDescriptor <Solution_ > variableDescriptor ,
264- SelectionCacheType minimumCacheType , boolean randomSelection ) {
312+ SelectionSorter < Solution_ , Object > sorter , SelectionCacheType minimumCacheType , boolean randomSelection ) {
265313 var valueRangeDescriptor = variableDescriptor .getValueRangeDescriptor ();
266314 // TODO minimumCacheType SOLVER is only a problem if the valueRange includes entities or custom weird cloning
267315 if (minimumCacheType == SelectionCacheType .SOLVER ) {
@@ -271,11 +319,13 @@ private ValueSelector<Solution_> buildBaseValueSelector(GenuineVariableDescripto
271319 + ") is not yet supported. Please use " + SelectionCacheType .PHASE + " instead." );
272320 }
273321 if (valueRangeDescriptor .canExtractValueRangeFromSolution ()) {
274- return new IterableFromSolutionPropertyValueSelector <>(valueRangeDescriptor , minimumCacheType , randomSelection );
322+ return new IterableFromSolutionPropertyValueSelector <>(valueRangeDescriptor , sorter , minimumCacheType ,
323+ randomSelection );
275324 } else {
276325 // TODO Do not allow PHASE cache on FromEntityPropertyValueSelector, except if the moveSelector is PHASE cached too.
277- var fromEntityPropertySelector = new FromEntityPropertyValueSelector <>(valueRangeDescriptor , randomSelection );
278- return new IterableFromEntityPropertyValueSelector <>(fromEntityPropertySelector , randomSelection );
326+ var fromEntityPropertySelector =
327+ new FromEntityPropertyValueSelector <>(valueRangeDescriptor , sorter , randomSelection );
328+ return new IterableFromEntityPropertyValueSelector <>(fromEntityPropertySelector , minimumCacheType , randomSelection );
279329 }
280330 }
281331
@@ -372,37 +422,7 @@ private static void assertNotSorterClassAnd(ValueSelectorConfig config, String p
372422 protected ValueSelector <Solution_ > applySorting (SelectionCacheType resolvedCacheType , SelectionOrder resolvedSelectionOrder ,
373423 ValueSelector <Solution_ > valueSelector , ClassInstanceCache instanceCache ) {
374424 if (resolvedSelectionOrder == SelectionOrder .SORTED ) {
375- SelectionSorter <Solution_ , Object > sorter ;
376- var sorterManner = config .getSorterManner ();
377- var comparatorClass = determineComparatorClass (config );
378- var comparatorFactoryClass = determineComparatorFactoryClass (config );
379- if (sorterManner != null ) {
380- var variableDescriptor = valueSelector .getVariableDescriptor ();
381- if (!ValueSelectorConfig .hasSorter (sorterManner , variableDescriptor )) {
382- return valueSelector ;
383- }
384- sorter = ValueSelectorConfig .determineSorter (sorterManner , variableDescriptor );
385- } else if (comparatorClass != null ) {
386- Comparator <Object > sorterComparator =
387- instanceCache .newInstance (config , determineComparatorPropertyName (config ), comparatorClass );
388- sorter = new ComparatorSelectionSorter <>(sorterComparator ,
389- SelectionSorterOrder .resolve (config .getSorterOrder ()));
390- } else if (comparatorFactoryClass != null ) {
391- var comparatorFactory = instanceCache .newInstance (config , determineComparatorFactoryPropertyName (config ),
392- comparatorFactoryClass );
393- sorter = new ComparatorFactorySelectionSorter <>(comparatorFactory ,
394- SelectionSorterOrder .resolve (config .getSorterOrder ()));
395- } else if (config .getSorterClass () != null ) {
396- sorter = instanceCache .newInstance (config , "sorterClass" , config .getSorterClass ());
397- } else {
398- throw new IllegalArgumentException ("""
399- The valueSelectorConfig (%s) with resolvedSelectionOrder (%s) needs \
400- a sorterManner (%s) or a %s (%s) or a %s (%s) \
401- or a sorterClass (%s)."""
402- .formatted (config , resolvedSelectionOrder , sorterManner , determineComparatorPropertyName (config ),
403- comparatorClass , determineComparatorFactoryPropertyName (config ), comparatorFactoryClass ,
404- config .getSorterClass ()));
405- }
425+ var sorter = determineSorter (valueSelector .getVariableDescriptor (), resolvedSelectionOrder , instanceCache );
406426 if (!valueSelector .getVariableDescriptor ().canExtractValueRangeFromSolution ()
407427 && resolvedCacheType == SelectionCacheType .STEP ) {
408428 valueSelector = new FromEntitySortingValueSelector <>(valueSelector , resolvedCacheType , sorter );
0 commit comments