Skip to content

Commit 4b749eb

Browse files
committed
chore: reconfiguration strategy compatible with MT
1 parent 7fa4925 commit 4b749eb

File tree

5 files changed

+38
-23
lines changed

5 files changed

+38
-23
lines changed

core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public LocalSearchPhase<Solution_> buildPhase(int phaseIndex, boolean triggerFir
7070
private LocalSearchDecider<Solution_> buildDecider(HeuristicConfigPolicy<Solution_> configPolicy,
7171
Termination<Solution_> termination) {
7272
var moveSelector = buildMoveSelector(configPolicy);
73-
var reconfigurationStrategy = buildReconfigurationStrategy(configPolicy);
7473
var acceptor = buildAcceptor(configPolicy);
74+
var reconfigurationStrategy = buildReconfigurationStrategy(configPolicy, moveSelector, acceptor);
7575
var forager = buildForager(configPolicy);
7676
if (moveSelector.isNeverEnding() && !forager.supportsNeverEndingMoveSelector()) {
7777
throw new IllegalStateException("The moveSelector (" + moveSelector
@@ -97,9 +97,6 @@ private LocalSearchDecider<Solution_> buildDecider(HeuristicConfigPolicy<Solutio
9797
if (environmentMode.isIntrusiveFastAsserted()) {
9898
decider.setAssertExpectedUndoMoveScore(true);
9999
}
100-
if (reconfigurationStrategy instanceof RestoreBestSolutionReconfigurationStrategy<Solution_> castReconfigurationStrategy) {
101-
castReconfigurationStrategy.setDecider(decider);
102-
}
103100
return decider;
104101
}
105102

@@ -206,14 +203,15 @@ protected MoveSelector<Solution_> buildMoveSelector(HeuristicConfigPolicy<Soluti
206203
return moveSelector;
207204
}
208205

209-
private ReconfigurationStrategy<Solution_> buildReconfigurationStrategy(HeuristicConfigPolicy<Solution_> configPolicy) {
206+
private ReconfigurationStrategy<Solution_> buildReconfigurationStrategy(HeuristicConfigPolicy<Solution_> configPolicy,
207+
MoveSelector<Solution_> moveSelector, Acceptor<Solution_> acceptor) {
210208
var acceptorConfig = phaseConfig.getAcceptorConfig();
211209
if (acceptorConfig != null) {
212210
var enableReconfiguration =
213211
acceptorConfig.getEnableReconfiguration() != null && acceptorConfig.getEnableReconfiguration();
214212
if (enableReconfiguration) {
215213
configPolicy.ensurePreviewFeature(PreviewFeature.RECONFIGURATION);
216-
return new RestoreBestSolutionReconfigurationStrategy<>();
214+
return new RestoreBestSolutionReconfigurationStrategy<>(moveSelector, acceptor);
217215
}
218216
}
219217
return new NoOpReconfigurationStrategy<>();

core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/ReconfigurableAcceptor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope;
66
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope;
77
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchStepScope;
8+
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
89

910
/**
1011
* Base class designed to analyze whether the solving process needs to be restarted.
@@ -24,6 +25,12 @@ protected boolean isEnabled() {
2425
return enabled;
2526
}
2627

28+
@Override
29+
public void solvingStarted(SolverScope<Solution_> solverScope) {
30+
super.solvingStarted(solverScope);
31+
restartStrategy.solvingStarted(solverScope);
32+
}
33+
2734
@Override
2835
public void phaseStarted(LocalSearchPhaseScope<Solution_> phaseScope) {
2936
super.phaseStarted(phaseScope);

core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/restart/UnimprovedTimeGeometricRestartStrategy.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ protected UnimprovedTimeGeometricRestartStrategy(Clock clock) {
4242
@Override
4343
public void phaseStarted(LocalSearchPhaseScope<Solution_> phaseScope) {
4444
currentBestScore = phaseScope.getBestScore();
45-
geometricGrowFactor = 1;
46-
nextRestart = (long) (1_000 * SCALING_FACTOR);
45+
if (lastImprovementMillis == 0) {
46+
lastImprovementMillis = clock.millis();
47+
}
4748
restartTriggered = false;
48-
lastImprovementMillis = clock.millis();
4949
}
5050

5151
@Override
@@ -69,7 +69,9 @@ public void stepEnded(LocalSearchStepScope<Solution_> stepScope) {
6969

7070
@Override
7171
public void solvingStarted(SolverScope<Solution_> solverScope) {
72-
// Do nothing
72+
lastImprovementMillis = 0;
73+
geometricGrowFactor = 1;
74+
nextRestart = (long) (1_000 * SCALING_FACTOR);
7375
}
7476

7577
@Override

core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/reconfiguration/RestoreBestSolutionReconfigurationStrategy.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import java.util.Objects;
44

55
import ai.timefold.solver.core.api.score.Score;
6-
import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider;
6+
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
7+
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.Acceptor;
78
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope;
89
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
910
import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope;
@@ -15,10 +16,12 @@
1516
public final class RestoreBestSolutionReconfigurationStrategy<Solution_> implements ReconfigurationStrategy<Solution_> {
1617

1718
private final Logger logger = LoggerFactory.getLogger(getClass());
18-
private LocalSearchDecider<Solution_> decider;
19+
private final MoveSelector<Solution_> moveSelector;
20+
private final Acceptor<Solution_> acceptor;
1921

20-
public void setDecider(LocalSearchDecider<Solution_> decider) {
21-
this.decider = decider;
22+
public RestoreBestSolutionReconfigurationStrategy(MoveSelector<Solution_> moveSelector, Acceptor<Solution_> acceptor) {
23+
this.moveSelector = moveSelector;
24+
this.acceptor = acceptor;
2225
}
2326

2427
@Override
@@ -29,8 +32,9 @@ public <Score_ extends Score<Score_>> Score_ apply(AbstractStepScope<Solution_>
2932
solverScope.setWorkingSolutionFromBestSolution();
3033
// Changing the working solution requires reinitializing the move selector and acceptor
3134
// 1 - The move selector will reset all cached lists using old solution entity references
35+
moveSelector.phaseStarted(stepScope.getPhaseScope());
3236
// 2 - The acceptor will restart its search from the updated working solution (last best solution)
33-
decider.phaseStarted((LocalSearchPhaseScope<Solution_>) stepScope.getPhaseScope());
37+
acceptor.phaseStarted((LocalSearchPhaseScope<Solution_>) stepScope.getPhaseScope());
3438
// Reset it as the best solution is already restored
3539
stepScope.getPhaseScope().resetReconfiguration();
3640
return (Score_) solverScope.getBestScore();
@@ -48,7 +52,8 @@ public void stepEnded(AbstractStepScope<Solution_> stepScope) {
4852

4953
@Override
5054
public void phaseStarted(AbstractPhaseScope<Solution_> phaseScope) {
51-
Objects.requireNonNull(decider);
55+
Objects.requireNonNull(moveSelector);
56+
Objects.requireNonNull(acceptor);
5257
}
5358

5459
@Override

core/src/test/java/ai/timefold/solver/core/impl/localsearch/decider/reconfiguration/ReconfigurationStrategyTest.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import static org.assertj.core.api.Assertions.assertThatThrownBy;
55
import static org.mockito.Mockito.*;
66

7-
import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider;
7+
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
8+
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.Acceptor;
89
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.restart.NoOpRestartStrategy;
910
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope;
1011
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope;
@@ -23,24 +24,26 @@ void noOp() {
2324

2425
@Test
2526
void restoreBestSolution() {
26-
var decider = mock(LocalSearchDecider.class);
27-
var strategy = new RestoreBestSolutionReconfigurationStrategy<>();
28-
2927
// Requires the decider
30-
assertThatThrownBy(() -> strategy.phaseStarted(null)).isInstanceOf(NullPointerException.class);
28+
assertThatThrownBy(() -> new RestoreBestSolutionReconfigurationStrategy<>(null, null).phaseStarted(null))
29+
.isInstanceOf(NullPointerException.class);
3130

3231
// Restore the best solution
32+
var moveSelector = mock(MoveSelector.class);
33+
var acceptor = mock(Acceptor.class);
34+
var strategy = new RestoreBestSolutionReconfigurationStrategy<>(moveSelector, acceptor);
35+
3336
var solverScope = mock(SolverScope.class);
3437
var phaseScope = mock(LocalSearchPhaseScope.class);
3538
when(phaseScope.getSolverScope()).thenReturn(solverScope);
3639
var stepScope = mock(LocalSearchStepScope.class);
3740
when(stepScope.getPhaseScope()).thenReturn(phaseScope);
38-
strategy.setDecider(decider);
3941
strategy.apply(stepScope);
4042
// Restore the best solution
4143
verify(solverScope, times(1)).setWorkingSolutionFromBestSolution();
4244
// Restart the phase
4345
verify(phaseScope, times(1)).resetReconfiguration();
44-
verify(decider, times(1)).phaseStarted(any());
46+
verify(moveSelector, times(1)).phaseStarted(any());
47+
verify(acceptor, times(1)).phaseStarted(any());
4548
}
4649
}

0 commit comments

Comments
 (0)