Skip to content

Commit 4b24bb0

Browse files
fix: seperate move and solver RandomGenerator usage (TimefoldAI#2356)
Co-authored-by: Lukáš Petrovický <lukas@timefold.ai>
1 parent b35c255 commit 4b24bb0

85 files changed

Lines changed: 511 additions & 287 deletions

File tree

Some content is hidden

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

core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ protected EntityDescriptor<Solution_> deduceEntityDescriptor(SolutionDescriptor<
162162
boolean scoreBounderEnabled, boolean isListVariable) {
163163
var manualEntityMimicRecorder = new ManualEntityMimicRecorder<>(sourceEntitySelector);
164164
var entityClassName = sourceEntitySelector.getEntityDescriptor().getEntityClass().getName();
165-
var mimicSelectorId = ConfigUtils.addRandomSuffix(entityClassName, configPolicy.getRandom());
165+
var mimicSelectorId = ConfigUtils.addRandomSuffix(entityClassName, configPolicy.getRandom().factoryUsage());
166166
configPolicy.addEntityMimicRecorder(mimicSelectorId, manualEntityMimicRecorder);
167167
var variableDescriptorList = getGenuineVariableDescriptorList(sourceEntitySelector, isListVariable);
168168
MoveSelectorConfig<?> moveSelectorConfig = phaseConfig.getMoveSelectorConfig();

core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.Map;
66
import java.util.Set;
77
import java.util.concurrent.ThreadFactory;
8-
import java.util.random.RandomGenerator;
98

109
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner;
1110
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner;
@@ -23,6 +22,7 @@
2322
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
2423
import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend;
2524
import ai.timefold.solver.core.impl.solver.ClassInstanceCache;
25+
import ai.timefold.solver.core.impl.solver.random.RandomSource;
2626
import ai.timefold.solver.core.impl.solver.thread.ChildThreadType;
2727
import ai.timefold.solver.core.impl.solver.thread.DefaultSolverThreadFactory;
2828

@@ -42,7 +42,7 @@ public class HeuristicConfigPolicy<Solution_> {
4242
private final boolean reinitializeVariableFilterEnabled;
4343
private final boolean unassignedValuesAllowed;
4444
private final Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
45-
private final RandomGenerator random;
45+
private final RandomSource random;
4646

4747
private final Map<String, EntityMimicRecorder<Solution_>> entityMimicRecorderMap = new HashMap<>();
4848
private final Map<String, SubListMimicRecorder<Solution_>> subListMimicRecorderMap = new HashMap<>();
@@ -118,7 +118,7 @@ public Class<? extends NearbyDistanceMeter> getNearbyDistanceMeterClass() {
118118
return nearbyDistanceMeterClass;
119119
}
120120

121-
public RandomGenerator getRandom() {
121+
public RandomSource getRandom() {
122122
return random;
123123
}
124124

@@ -275,7 +275,7 @@ public static class Builder<Solution_> {
275275
private boolean unassignedValuesAllowed = false;
276276

277277
private Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
278-
private RandomGenerator random;
278+
private RandomSource random;
279279

280280
public Builder<Solution_> withPreviewFeatureSet(Set<PreviewFeature> previewFeatureSet) {
281281
this.previewFeatureSet = previewFeatureSet;
@@ -308,7 +308,7 @@ public Builder<Solution_> withThreadFactoryClass(Class<? extends ThreadFactory>
308308
return this;
309309
}
310310

311-
public Builder<Solution_> withRandom(RandomGenerator random) {
311+
public Builder<Solution_> withRandom(RandomSource random) {
312312
this.random = random;
313313
return this;
314314
}

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/AbstractSelector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public abstract class AbstractSelector<Solution_> implements Selector<Solution_>
2626

2727
@Override
2828
public void solvingStarted(SolverScope<Solution_> solverScope) {
29-
workingRandom = solverScope.getWorkingRandom();
29+
workingRandom = solverScope.getWorkingRandom().moveIteratorUsage();
3030
phaseLifecycleSupport.fireSolvingStarted(solverScope);
3131
}
3232

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The selector configuration (%s) already includes the Nearby Selection setting, m
4949
// Add a new configuration with Nearby Selection enabled
5050
moveSelectorConfigList
5151
.add(nearbySelectorConfig.enableNearbySelection(configPolicy.getNearbyDistanceMeterClass(),
52-
configPolicy.getRandom()));
52+
configPolicy.getRandom().factoryUsage()));
5353

5454
}
5555
}

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveIterator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ protected Move<Solution_> createUpcomingSelection() {
6868
}
6969
}
7070
return new SelectorBasedRuinRecreateMove<>(variableDescriptor, constructionHeuristicPhaseBuilder, solverScope,
71-
selectedEntityList, affectedValueSet);
71+
selectedEntityList, affectedValueSet, workingRandom.nextLong());
7272
}
7373

7474
}

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateMoveSelector.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,12 @@ public boolean isNeverEnding() {
5555
public void solvingStarted(SolverScope<Solution_> solverScope) {
5656
super.solvingStarted(solverScope);
5757
this.solverScope = solverScope;
58-
this.workingRandom = solverScope.getWorkingRandom();
5958
}
6059

6160
@Override
6261
public void phaseEnded(AbstractPhaseScope<Solution_> phaseScope) {
6362
super.phaseEnded(phaseScope);
6463
this.solverScope = null;
65-
this.workingRandom = null;
6664
}
6765

6866
@Override

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector;
1313
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
1414
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
15+
import ai.timefold.solver.core.impl.solver.random.DefaultRandomSource;
1516
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
1617
import ai.timefold.solver.core.preview.api.move.Move;
1718

@@ -26,18 +27,38 @@ public final class SelectorBasedRuinRecreateMove<Solution_> extends AbstractSele
2627
private final SolverScope<Solution_> solverScope;
2728
private final List<Object> ruinedEntityList;
2829
private final SequencedSet<Object> affectedValueSet;
30+
private final long randomSeed;
2931

3032
private Object @Nullable [] recordedNewValues;
3133

34+
/**
35+
* Prefer
36+
* {@link #SelectorBasedRuinRecreateMove(GenuineVariableDescriptor, RuinRecreateConstructionHeuristicPhaseBuilder, SolverScope, List, SequencedSet, long)}
37+
* with explicitly provided random seed.
38+
*
39+
* @param genuineVariableDescriptor
40+
* @param constructionHeuristicPhaseBuilder
41+
* @param solverScope
42+
* @param ruinedEntityList
43+
* @param affectedValueSet
44+
*/
3245
public SelectorBasedRuinRecreateMove(GenuineVariableDescriptor<Solution_> genuineVariableDescriptor,
3346
RuinRecreateConstructionHeuristicPhaseBuilder<Solution_> constructionHeuristicPhaseBuilder,
3447
SolverScope<Solution_> solverScope, List<Object> ruinedEntityList, SequencedSet<Object> affectedValueSet) {
48+
this(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope, ruinedEntityList, affectedValueSet, 0);
49+
}
50+
51+
public SelectorBasedRuinRecreateMove(GenuineVariableDescriptor<Solution_> genuineVariableDescriptor,
52+
RuinRecreateConstructionHeuristicPhaseBuilder<Solution_> constructionHeuristicPhaseBuilder,
53+
SolverScope<Solution_> solverScope, List<Object> ruinedEntityList, SequencedSet<Object> affectedValueSet,
54+
long randomSeed) {
3555
this.genuineVariableDescriptor = genuineVariableDescriptor;
3656
this.ruinedEntityList = ruinedEntityList;
3757
this.affectedValueSet = affectedValueSet;
3858
this.constructionHeuristicPhaseBuilder = constructionHeuristicPhaseBuilder;
3959
this.solverScope = solverScope;
4060
this.recordedNewValues = null;
61+
this.randomSeed = randomSeed;
4162
}
4263

4364
@Override
@@ -65,6 +86,7 @@ protected void execute(VariableDescriptorAwareScoreDirector<Solution_> scoreDire
6586
var nestedSolverScope = new SolverScope<Solution_>(solverScope.getClock());
6687
nestedSolverScope.setSolver(solverScope.getSolver());
6788
nestedSolverScope.setScoreDirector(innerScoreDirector);
89+
nestedSolverScope.setWorkingRandom(DefaultRandomSource.seeded(randomSeed));
6890
constructionHeuristicPhase.solvingStarted(nestedSolverScope);
6991
constructionHeuristicPhase.solve(nestedSolverScope);
7092
constructionHeuristicPhase.solvingEnded(nestedSolverScope);
@@ -90,7 +112,7 @@ public Move<Solution_> rebase(Lookup lookup) {
90112
var rebasedRuinedEntityList = rebaseList(ruinedEntityList, lookup);
91113
var rebasedAffectedValueSet = rebaseSet(affectedValueSet, lookup);
92114
return new SelectorBasedRuinRecreateMove<>(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope,
93-
rebasedRuinedEntityList, rebasedAffectedValueSet);
115+
rebasedRuinedEntityList, rebasedAffectedValueSet, randomSeed);
94116
}
95117

96118
@Override

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
5151
if (entitySelectorConfig.getId() == null && entitySelectorConfig.getMimicSelectorRef() == null) {
5252
var entityName = Objects.requireNonNull(entityDescriptor.getEntityClass().getSimpleName());
5353
// We set the id to make sure the value selector will use the mimic recorder
54-
entityRecorderId = ConfigUtils.addRandomSuffix(entityName, configPolicy.getRandom());
54+
entityRecorderId = ConfigUtils.addRandomSuffix(entityName, configPolicy.getRandom().factoryUsage());
5555
entitySelectorConfig.setId(entityRecorderId);
5656
} else {
5757
entityRecorderId = entitySelectorConfig.getId() != null ? entitySelectorConfig.getId()

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
8383
if (mimicSelectorConfig.getMimicSelectorRef() == null && mimicSelectorConfig.getId() == null) {
8484
var variableName = Objects.requireNonNull(mimicSelectorConfig.getVariableName());
8585
// We set the id to make sure the value selector will use the mimic recorder
86-
entityValueRangeRecorderId = ConfigUtils.addRandomSuffix(variableName, configPolicy.getRandom());
86+
entityValueRangeRecorderId = ConfigUtils.addRandomSuffix(variableName, configPolicy.getRandom().factoryUsage());
8787
mimicSelectorConfig.setId(entityValueRangeRecorderId);
8888
} else {
8989
entityValueRangeRecorderId =

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListSwapMoveSelectorFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
5050
if (valueSelectorConfig.getId() == null && valueSelectorConfig.getMimicSelectorRef() == null) {
5151
var variableName = Objects.requireNonNull(valueSelectorConfig.getVariableName());
5252
// We set the id to make sure the value selector will use the mimic recorder
53-
entityValueRangeRecorderId = ConfigUtils.addRandomSuffix(variableName, configPolicy.getRandom());
53+
entityValueRangeRecorderId = ConfigUtils.addRandomSuffix(variableName, configPolicy.getRandom().factoryUsage());
5454
valueSelectorConfig.setId(entityValueRangeRecorderId);
5555
} else {
5656
entityValueRangeRecorderId = valueSelectorConfig.getId() != null ? valueSelectorConfig.getId()

0 commit comments

Comments
 (0)