Skip to content

Commit 5c54b5e

Browse files
committed
fix: improve strategy
1 parent b6e9083 commit 5c54b5e

2 files changed

Lines changed: 17 additions & 10 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public JustInTimeFilteringMoveIterator(Iterator<Move<Solution_>> childMoveIterat
9494
protected Move<Solution_> createUpcomingSelection() {
9595
Move<Solution_> next;
9696
long attemptsBeforeBailOut = bailOutSize;
97+
// To reduce the impact of checking for termination on each move,
98+
// we only check termination after evaluating 30% of the available moves
99+
long attemptsBeforeCheckTermination = (long) (bailOutSize * 0.3);
97100
do {
98101
if (!childMoveIterator.hasNext()) {
99102
return noUpcomingSelection();
@@ -104,13 +107,18 @@ protected Move<Solution_> createUpcomingSelection() {
104107
logger.trace("Bailing out of neverEnding selector ({}) after ({}) attempts to avoid infinite loop.",
105108
FilteringMoveSelector.this, bailOutSize);
106109
return noUpcomingSelection();
107-
} else if (termination != null && termination.isPhaseTerminated(phaseScope)) {
108-
logger.trace(
109-
"Bailing out of neverEnding selector ({}) because the termination setting has been triggered.",
110-
FilteringMoveSelector.this);
111-
return noUpcomingSelection();
110+
} else if (termination != null && attemptsBeforeCheckTermination <= 0L) {
111+
// Reset the counter
112+
attemptsBeforeCheckTermination = (long) (bailOutSize * 0.3);
113+
if (termination.isPhaseTerminated(phaseScope)) {
114+
logger.trace(
115+
"Bailing out of neverEnding selector ({}) because the termination setting has been triggered.",
116+
FilteringMoveSelector.this);
117+
return noUpcomingSelection();
118+
}
112119
}
113120
attemptsBeforeBailOut--;
121+
attemptsBeforeCheckTermination--;
114122
}
115123
next = childMoveIterator.next();
116124
} while (!accept(scoreDirector, next));

core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelectorTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,17 @@ void bailOutByTermination() {
5252
var moveSelector = mock(MoveSelector.class);
5353
var termination = mock(BasicPlumbingTermination.class);
5454
var iterator = mock(Iterator.class);
55-
// We set the maximum value to force it to run many evaluations
56-
when(moveSelector.getSize()).thenReturn(Long.MAX_VALUE / 11);
55+
when(moveSelector.getSize()).thenReturn(100L);
5756
when(moveSelector.isNeverEnding()).thenReturn(true);
5857
when(moveSelector.iterator()).thenReturn(iterator);
5958
when(iterator.hasNext()).thenReturn(true);
6059
when(phaseScope.getTermination()).thenReturn(termination);
61-
when(termination.isPhaseTerminated(any(AbstractPhaseScope.class))).thenReturn(false, false, true);
60+
when(termination.isPhaseTerminated(any(AbstractPhaseScope.class))).thenReturn(false, true);
6261
var filteredMoveSelector = FilteringMoveSelector.of(moveSelector, (scoreDirector, selection) -> false);
6362
filteredMoveSelector.phaseStarted(phaseScope);
6463
assertThat(filteredMoveSelector.iterator().hasNext()).isFalse();
65-
// The termination returns true at the third call
66-
verify(iterator, times(2)).next();
64+
// The termination returns true at the second call, and only (100 * 10) * 30% calls are executed per check
65+
verify(iterator, times(600)).next();
6766
}
6867

6968
public void filter(SelectionCacheType cacheType, int timesCalled) {

0 commit comments

Comments
 (0)