Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@
import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig;
import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig;
import ai.timefold.solver.core.config.solver.monitoring.SolverMetric;
import ai.timefold.solver.core.config.solver.random.RandomType;
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO;
import ai.timefold.solver.core.impl.io.jaxb.TimefoldXmlSerializationException;
import ai.timefold.solver.core.impl.phase.PhaseFactory;
import ai.timefold.solver.core.impl.solver.random.RandomFactory;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
Expand All @@ -63,9 +61,7 @@
"enablePreviewFeatureSet",
"environmentMode",
"daemon",
"randomType",
"randomSeed",
"randomFactoryClass",
"moveThreadCount",
"moveThreadBufferSize",
"threadFactoryClass",
Expand Down Expand Up @@ -215,9 +211,7 @@ public final class SolverConfig extends AbstractConfig<SolverConfig> {
private Set<PreviewFeature> enablePreviewFeatureSet = null;
private EnvironmentMode environmentMode = null;
private Boolean daemon = null;
private RandomType randomType = null;
private Long randomSeed = null;
private Class<? extends RandomFactory> randomFactoryClass = null;
private String moveThreadCount = null;
private Integer moveThreadBufferSize = null;
private Class<? extends ThreadFactory> threadFactoryClass = null;
Expand Down Expand Up @@ -332,14 +326,6 @@ public void setDaemon(@Nullable Boolean daemon) {
this.daemon = daemon;
}

public @Nullable RandomType getRandomType() {
return randomType;
}

public void setRandomType(@Nullable RandomType randomType) {
this.randomType = randomType;
}

public @Nullable Long getRandomSeed() {
return randomSeed;
}
Expand All @@ -348,14 +334,6 @@ public void setRandomSeed(@Nullable Long randomSeed) {
this.randomSeed = randomSeed;
}

public @Nullable Class<? extends RandomFactory> getRandomFactoryClass() {
return randomFactoryClass;
}

public void setRandomFactoryClass(@Nullable Class<? extends RandomFactory> randomFactoryClass) {
this.randomFactoryClass = randomFactoryClass;
}

public @Nullable String getMoveThreadCount() {
return moveThreadCount;
}
Expand Down Expand Up @@ -471,21 +449,11 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) {
return this;
}

public @NonNull SolverConfig withRandomType(@NonNull RandomType randomType) {
this.randomType = randomType;
return this;
}

public @NonNull SolverConfig withRandomSeed(@NonNull Long randomSeed) {
this.randomSeed = randomSeed;
return this;
}

public @NonNull SolverConfig withRandomFactoryClass(@NonNull Class<? extends RandomFactory> randomFactoryClass) {
this.randomFactoryClass = randomFactoryClass;
return this;
}

public @NonNull SolverConfig withMoveThreadCount(@NonNull String moveThreadCount) {
this.moveThreadCount = moveThreadCount;
return this;
Expand Down Expand Up @@ -648,7 +616,7 @@ public boolean canTerminate() {
// ************************************************************************

public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
if ((environmentMode == null || environmentMode.isReproducible()) && randomFactoryClass == null && randomSeed == null) {
if ((environmentMode == null || environmentMode.isReproducible()) && randomSeed == null) {
randomSeed = subSingleIndex;
}
}
Expand All @@ -665,10 +633,7 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
inheritedConfig.getEnablePreviewFeatureSet());
environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode());
daemon = ConfigUtils.inheritOverwritableProperty(daemon, inheritedConfig.getDaemon());
randomType = ConfigUtils.inheritOverwritableProperty(randomType, inheritedConfig.getRandomType());
randomSeed = ConfigUtils.inheritOverwritableProperty(randomSeed, inheritedConfig.getRandomSeed());
randomFactoryClass = ConfigUtils.inheritOverwritableProperty(randomFactoryClass,
inheritedConfig.getRandomFactoryClass());
moveThreadCount = ConfigUtils.inheritOverwritableProperty(moveThreadCount,
inheritedConfig.getMoveThreadCount());
moveThreadBufferSize = ConfigUtils.inheritOverwritableProperty(moveThreadBufferSize,
Expand Down Expand Up @@ -700,7 +665,6 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {

@Override
public void visitReferencedClasses(@NonNull Consumer<Class<?>> classVisitor) {
classVisitor.accept(randomFactoryClass);
classVisitor.accept(threadFactoryClass);
classVisitor.accept(solutionClass);
if (entityClassList != null) {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ public void solve(SolverScope<Solution_> solverScope) {
stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow());
}
} else {
throw new IllegalStateException("The step index (" + stepScope.getStepIndex()
+ ") has selected move count (" + stepScope.getSelectedMoveCount()
+ ") but failed to pick a nextStep (" + stepScope.getStep() + ").");
throw new IllegalStateException(
"The step index (%d) has selected move count (%d) but failed to pick a nextStep (%s).".formatted(
stepScope.getStepIndex(), stepScope.getSelectedMoveCount(), stepScope.getStep()));
}
// Although stepStarted has been called, stepEnded is not called for this step.
earlyTerminationStatus = TerminationStatus.early(phaseScope.getNextStepIndex());
Expand Down Expand Up @@ -190,11 +190,16 @@ public void phaseEnded(ConstructionHeuristicPhaseScope<Solution_> phaseScope) {
phaseScope.endingNow();
if (decider.isLoggingEnabled() && logger.isInfoEnabled()) {
logger.info(
"{}Construction Heuristic phase ({}) ended: time spent ({}), best score ({}), move evaluation speed ({}/sec), step total ({}).",
"""
{}Construction Heuristic phase ({}) ended: time spent ({}), best score ({}), \
{}move evaluation speed ({}/sec), step total ({}).""",
logIndentation,
phaseIndex,
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
phaseScope.getBestScore().raw(),
// Multithreaded solving uses "effective" move evaluation speed, since not all evaluated moves
// are foraged
(decider.getClass().equals(ConstructionHeuristicDecider.class)) ? "" : "effective ",
phaseScope.getPhaseMoveEvaluationSpeed(),
phaseScope.getNextStepIndex());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ private void phaseEnded(ExhaustiveSearchPhaseScope<Solution_> phaseScope) {
super.phaseEnded(phaseScope);
decider.phaseEnded(phaseScope);
phaseScope.endingNow();
logger.info("{}Exhaustive Search phase ({}) ended: time spent ({}), best score ({}),"
+ " move evaluation speed ({}/sec), step total ({}).",
logger.info("""
{}Exhaustive Search phase ({}) ended: time spent ({}), best score ({}),\
move evaluation speed ({}/sec), step total ({}).""",
logIndentation,
phaseIndex,
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,17 @@ public void solve(SolverScope<Solution_> solverScope) {
stepScope.getStepIndex(),
stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow());
} else if (stepScope.getSelectedMoveCount() == 0L) {
logger.warn("{} No doable selected move at step index ({}), time spent ({})."
+ " Terminating phase early.",
logger.warn("""
{} No doable selected move at step index ({}), time spent ({}). \
Terminating phase early.""",
logIndentation,
stepScope.getStepIndex(),
stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow());
} else {
throw new IllegalStateException("The step index (" + stepScope.getStepIndex()
+ ") has accepted/selected move count (" + stepScope.getAcceptedMoveCount() + "/"
+ stepScope.getSelectedMoveCount()
+ ") but failed to pick a nextStep (" + stepScope.getStep() + ").");
throw new IllegalStateException(
"The step index (%d) has accepted/selected move count (%d/%d) but failed to pick a nextStep (%s)."
.formatted(stepScope.getStepIndex(), stepScope.getAcceptedMoveCount(),
stepScope.getSelectedMoveCount(), stepScope.getStep()));
}
// Although stepStarted has been called, stepEnded is not called for this step
break;
Expand Down Expand Up @@ -151,17 +152,19 @@ public void stepEnded(LocalSearchStepScope<Solution_> stepScope) {
if (logger.isDebugEnabled()) {
if (stepScope.getAcceptedMoveCount() == 0 && phaseTermination.isPhaseTerminated(phaseScope)) {
// Terminated early
logger.debug("{} LS step ({}), time spent ({}), score ({}), {} best score ({})," +
" terminated prematurely after selecting {} moves.",
logger.debug("""
{} LS step ({}), time spent ({}), score ({}), {} best score ({}), \
terminated prematurely after selecting {} moves.""",
logIndentation,
stepScope.getStepIndex(),
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
stepScope.getScore().raw(),
(stepScope.getBestScoreImproved() ? "new" : " "), phaseScope.getBestScore().raw(),
stepScope.getSelectedMoveCount());
} else {
logger.debug("{} LS step ({}), time spent ({}), score ({}), {} best score ({})," +
" accepted/selected move count ({}/{}), picked move ({}).",
logger.debug("""
{} LS step ({}), time spent ({}), score ({}), {} best score ({}), \
accepted/selected move count ({}/{}), picked move ({}).""",
logIndentation,
stepScope.getStepIndex(),
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
Expand Down Expand Up @@ -224,12 +227,16 @@ public void phaseEnded(LocalSearchPhaseScope<Solution_> phaseScope) {
super.phaseEnded(phaseScope);
decider.phaseEnded(phaseScope);
phaseScope.endingNow();
logger.info("{}Local Search phase ({}) ended: time spent ({}), best score ({}),"
+ " move evaluation speed ({}/sec), step total ({}).",
logger.info("""
{}Local Search phase ({}) ended: time spent ({}), best score ({}), \
{}move evaluation speed ({}/sec), step total ({}).""",
logIndentation,
phaseIndex,
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
phaseScope.getBestScore().raw(),
// Multithreaded solving uses "effective" move evaluation speed, since not all evaluated moves
// are foraged
(decider.getClass().equals(LocalSearchDecider.class)) ? "" : "effective ",
phaseScope.getPhaseMoveEvaluationSpeed(),
phaseScope.getNextStepIndex());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.random.RandomGenerator;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.solver.Solver;
Expand All @@ -12,11 +14,13 @@
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope;
import ai.timefold.solver.core.impl.solver.event.SolverEventSupport;
import ai.timefold.solver.core.impl.solver.random.DelegatingSplittableRandomGenerator;
import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.impl.solver.termination.UniversalTermination;

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -44,6 +48,8 @@ public abstract class AbstractSolver<Solution_> implements Solver<Solution_> {
protected final UniversalTermination<Solution_> globalTermination;
protected final List<Phase<Solution_>> phaseList;

private RandomGenerator.@Nullable SplittableGenerator savedRandom;

// ************************************************************************
// Constructors and simple getters/setters
// ************************************************************************
Expand Down Expand Up @@ -123,10 +129,18 @@ public void stepStarted(AbstractStepScope<Solution_> stepScope) {
bestSolutionRecaller.stepStarted(stepScope);
phaseLifecycleSupport.fireStepStarted(stepScope);
globalTermination.stepStarted(stepScope);
// To ensure reproducibility even when the number of random calls is not deterministic,
// split the random at step start.
var delegatingRandom = ((DelegatingSplittableRandomGenerator) stepScope.getWorkingRandom());
savedRandom = delegatingRandom.getDelegate();
delegatingRandom.setDelegate(delegatingRandom.split());
// Do not propagate to phases; the active phase does that for itself and they should not propagate further.
}

public void stepEnded(AbstractStepScope<Solution_> stepScope) {
// Restore from the split random
var delegatingRandom = ((DelegatingSplittableRandomGenerator) stepScope.getWorkingRandom());
delegatingRandom.setDelegate(Objects.requireNonNull(savedRandom));
bestSolutionRecaller.stepEnded(stepScope);
phaseLifecycleSupport.fireStepEnded(stepScope);
globalTermination.stepEnded(stepScope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.random.RandomGenerator;

import ai.timefold.solver.core.api.domain.common.PlanningId;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
Expand All @@ -16,7 +18,6 @@
import ai.timefold.solver.core.impl.phase.Phase;
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory;
import ai.timefold.solver.core.impl.solver.random.RandomFactory;
import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.impl.solver.termination.BasicPlumbingTermination;
Expand All @@ -38,7 +39,7 @@
public class DefaultSolver<Solution_> extends AbstractSolver<Solution_> {

protected final EnvironmentMode environmentMode;
protected final RandomFactory randomFactory;
protected final Supplier<RandomGenerator> randomFactory;
protected final BasicPlumbingTermination<Solution_> basicPlumbingTermination;
protected final AtomicBoolean solving = new AtomicBoolean(false);
protected final SolverScope<Solution_> solverScope;
Expand All @@ -48,7 +49,7 @@ public class DefaultSolver<Solution_> extends AbstractSolver<Solution_> {
// Constructors and simple getters/setters
// ************************************************************************

public DefaultSolver(EnvironmentMode environmentMode, RandomFactory randomFactory,
public DefaultSolver(EnvironmentMode environmentMode, Supplier<RandomGenerator> randomFactory,
BestSolutionRecaller<Solution_> bestSolutionRecaller, BasicPlumbingTermination<Solution_> basicPlumbingTermination,
UniversalTermination<Solution_> termination, List<Phase<Solution_>> phaseList,
SolverScope<Solution_> solverScope, String moveThreadCountDescription) {
Expand All @@ -65,8 +66,8 @@ public EnvironmentMode getEnvironmentMode() {
return environmentMode;
}

public RandomFactory getRandomFactory() {
return randomFactory;
public RandomGenerator getRandomGenerator() {
return randomFactory.get();
}

public ScoreDirectorFactory<Solution_, ?> getScoreDirectorFactory() {
Expand Down Expand Up @@ -187,7 +188,7 @@ public void outerSolvingStarted(SolverScope<Solution_> solverScope) {
solving.set(true);
basicPlumbingTermination.resetTerminateEarly();
solverScope.setStartingSolverCount(0);
solverScope.setWorkingRandom(randomFactory.createRandom());
solverScope.setWorkingRandom(randomFactory.get());
}

@Override
Expand Down
Loading
Loading