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
11 changes: 11 additions & 0 deletions core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@
"old": "method void ai.timefold.solver.core.api.solver.ProblemSizeStatistics::<init>(long, long, long, double)",
"new": "method void ai.timefold.solver.core.api.solver.ProblemSizeStatistics::<init>(long, java.util.SequencedMap<java.lang.Class<?>, java.lang.Long>, long, long, java.util.SequencedMap<java.lang.Class<?>, java.util.SequencedMap<java.lang.String, java.lang.Long>>, double)",
"justification": "Type is not supposed to be constructed by user; safe."
},
{
"ignore": true,
"code": "java.annotation.attributeValueChanged",
"old": "class ai.timefold.solver.core.config.phase.PhaseConfig<Config_ extends ai.timefold.solver.core.config.phase.PhaseConfig<Config_>>",
"new": "class ai.timefold.solver.core.config.phase.PhaseConfig<Config_ extends ai.timefold.solver.core.config.phase.PhaseConfig<Config_>>",
"annotationType": "jakarta.xml.bind.annotation.XmlType",
"attribute": "propOrder",
"oldValue": "{\"terminationConfig\"}",
"newValue": "{\"environmentMode\", \"terminationConfig\"}",
"justification": "Environment mode per phase"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig;
import ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig;
import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;
import ai.timefold.solver.core.config.util.ConfigUtils;

Expand All @@ -24,20 +25,32 @@
PartitionedSearchPhaseConfig.class
})
@XmlType(propOrder = {
"environmentMode",
"terminationConfig"
})
public abstract class PhaseConfig<Config_ extends PhaseConfig<Config_>> extends AbstractConfig<Config_> {

// Warning: all fields are null (and not defaulted) because they can be inherited
// and also because the input config file should match the output config file

// Per phase environment
protected EnvironmentMode environmentMode = null;

@XmlElement(name = "termination")
protected TerminationConfig terminationConfig = null;

// ************************************************************************
// Constructors and simple getters/setters
// ************************************************************************

public EnvironmentMode getEnvironmentMode() {
return environmentMode;
}

public void setEnvironmentMode(EnvironmentMode environmentMode) {
this.environmentMode = environmentMode;
}

public @Nullable TerminationConfig getTerminationConfig() {
return terminationConfig;
}
Expand All @@ -50,13 +63,19 @@ public void setTerminationConfig(@Nullable TerminationConfig terminationConfig)
// With methods
// ************************************************************************

public @NonNull Config_ withEnvironmentMode(@NonNull EnvironmentMode environmentMode) {
this.setEnvironmentMode(environmentMode);
return (Config_) this;
}

public @NonNull Config_ withTerminationConfig(@NonNull TerminationConfig terminationConfig) {
this.setTerminationConfig(terminationConfig);
return (Config_) this;
}

@Override
public @NonNull Config_ inherit(@NonNull Config_ inheritedConfig) {
environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode());
terminationConfig = ConfigUtils.inheritConfig(terminationConfig, inheritedConfig.getTerminationConfig());
return (Config_) this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ <Solution_> LocalSearchDecider<Solution_> buildLocalSearch(int moveThreadCount,
EnvironmentMode environmentMode, HeuristicConfigPolicy<Solution_> configPolicy);

<Solution_> PartitionedSearchPhase<Solution_> buildPartitionedSearch(int phaseIndex,
PartitionedSearchPhaseConfig phaseConfig, HeuristicConfigPolicy<Solution_> solverConfigPolicy,
SolverTermination<Solution_> solverTermination,
PartitionedSearchPhaseConfig phaseConfig, EnvironmentMode environmentMode,
HeuristicConfigPolicy<Solution_> solverConfigPolicy, SolverTermination<Solution_> solverTermination,
BiFunction<HeuristicConfigPolicy<Solution_>, SolverTermination<Solution_>, PhaseTermination<Solution_>> phaseTerminationFunction);

<Solution_> EntitySelector<Solution_> applyNearbySelection(EntitySelectorConfig entitySelectorConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,12 @@ public void phaseEnded(ConstructionHeuristicPhaseScope<Solution_> phaseScope) {
if (decider.isLoggingEnabled() && logger.isInfoEnabled()) {
logger.info(
"""
{}Construction Heuristic phase ({}) ended: time spent ({}), best score ({}), \
{}Construction Heuristic phase ({}) ended: time spent ({}), environment mode ({}), best score ({}), \
{}move evaluation speed ({}/sec), step total ({}).""",
logIndentation,
phaseIndex,
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
environmentMode.name(),
phaseScope.getBestScore().raw(),
// Multithreaded solving uses "effective" move evaluation speed, since not all evaluated moves
// are foraged
Expand Down Expand Up @@ -232,17 +233,17 @@ public static class DefaultConstructionHeuristicPhaseBuilder<Solution_>
private final EntityPlacer<Solution_> entityPlacer;
private final ConstructionHeuristicDecider<Solution_> decider;

public DefaultConstructionHeuristicPhaseBuilder(int phaseIndex, boolean lastInitializingPhase, String logIndentation,
PhaseTermination<Solution_> phaseTermination, EntityPlacer<Solution_> entityPlacer,
ConstructionHeuristicDecider<Solution_> decider) {
super(phaseIndex, lastInitializingPhase, logIndentation, phaseTermination);
public DefaultConstructionHeuristicPhaseBuilder(int phaseIndex, boolean lastInitializingPhase,
EnvironmentMode environmentMode, String logIndentation, PhaseTermination<Solution_> phaseTermination,
EntityPlacer<Solution_> entityPlacer, ConstructionHeuristicDecider<Solution_> decider) {
super(phaseIndex, lastInitializingPhase, environmentMode, logIndentation, phaseTermination);
this.entityPlacer = entityPlacer;
this.decider = decider;
}

@Override
public DefaultConstructionHeuristicPhaseBuilder<Solution_> enableAssertions(EnvironmentMode environmentMode) {
super.enableAssertions(environmentMode);
public DefaultConstructionHeuristicPhaseBuilder<Solution_> enableAssertions() {
Comment thread
zepfred marked this conversation as resolved.
super.enableAssertions();
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
import ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.DefaultConstructionHeuristicPhaseBuilder;
Expand Down Expand Up @@ -69,10 +70,11 @@ protected DefaultConstructionHeuristicPhaseBuilder<Solution_> createBuilder(
HeuristicConfigPolicy<Solution_> phaseConfigPolicy, SolverTermination<Solution_> solverTermination, int phaseIndex,
boolean lastInitializingPhase, EntityPlacer<Solution_> entityPlacer) {
var phaseTermination = buildPhaseTermination(phaseConfigPolicy, solverTermination);
return new DefaultConstructionHeuristicPhaseBuilder<>(phaseIndex, lastInitializingPhase,
var environmentMode = resolveEnvironmentMode(phaseConfigPolicy);
return new DefaultConstructionHeuristicPhaseBuilder<>(phaseIndex, lastInitializingPhase, environmentMode,
phaseConfigPolicy.getLogIndentation(), phaseTermination, entityPlacer,
buildDecider(phaseConfigPolicy, phaseTermination))
.enableAssertions(phaseConfigPolicy.getEnvironmentMode());
buildDecider(phaseConfigPolicy, environmentMode, phaseTermination))
.enableAssertions();
}

@Override
Expand Down Expand Up @@ -158,14 +160,14 @@ public static EntityPlacerConfig buildListVariableQueuedValuePlacerConfig(Heuris
}

protected ConstructionHeuristicDecider<Solution_> buildDecider(HeuristicConfigPolicy<Solution_> configPolicy,
PhaseTermination<Solution_> termination) {
EnvironmentMode environmentMode, PhaseTermination<Solution_> termination) {
var forager = buildForager(configPolicy);
var moveThreadCount = configPolicy.getMoveThreadCount();
var decider = (moveThreadCount == null)
? new ConstructionHeuristicDecider<>(configPolicy.getLogIndentation(), termination, forager)
: TimefoldSolverEnterpriseService.loadOrFail(TimefoldSolverEnterpriseService.Feature.MULTITHREADED_SOLVING)
.buildConstructionHeuristic(termination, forager, configPolicy);
decider.enableAssertions(configPolicy.getEnvironmentMode());
decider.enableAssertions(environmentMode);
return decider;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ai.timefold.solver.core.impl.domain.variable;

import java.util.Objects;

import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;

/**
* Demands a {@link ListVariableStateSupply} on {@link #phaseStarted(AbstractPhaseScope)} and releases it on
* {@link #phaseEnded(AbstractPhaseScope)}, re-demanding on every phase start because a phase may run under a
* different {@link ai.timefold.solver.core.config.solver.EnvironmentMode}, which swaps in a new score director
* (and thus a new {@link ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager}).
Comment thread
zepfred marked this conversation as resolved.
* <p>
* Intended to be held as a field by selectors that need a {@link ListVariableStateSupply} across phase lifecycle
* events, delegating their own {@code phaseStarted}/{@code phaseEnded} overrides to this holder instead of each
* re-implementing the demand/cancel bookkeeping.
*
* @param <Solution_> the solution type, the class with the {@link ai.timefold.solver.core.api.domain.solution.PlanningSolution}
* annotation
*/
public final class ListVariableStateSupplyHolder<Solution_> {

private final ListVariableDescriptor<Solution_> listVariableDescriptor;
private ListVariableStateSupply<Solution_, ?, ?> listVariableStateSupply;

public ListVariableStateSupplyHolder(ListVariableDescriptor<Solution_> listVariableDescriptor) {
this.listVariableDescriptor = listVariableDescriptor;
}

public void phaseStarted(AbstractPhaseScope<Solution_> phaseScope) {
listVariableStateSupply = phaseScope.getScoreDirector().getSupplyManager()
.demand(listVariableDescriptor.getStateDemand());
}

public void phaseEnded(AbstractPhaseScope<Solution_> phaseScope) {
phaseScope.getScoreDirector().getSupplyManager().cancel(listVariableDescriptor.getStateDemand());
listVariableStateSupply = null;
}

@SuppressWarnings("unchecked")
public <Entity_, Element_> ListVariableStateSupply<Solution_, Entity_, Element_> get() {
return (ListVariableStateSupply<Solution_, Entity_, Element_>) Objects.requireNonNull(listVariableStateSupply,
"Impossible state: The listVariableStateSupply is not initialized yet.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,12 @@ private void phaseEnded(ExhaustiveSearchPhaseScope<Solution_> phaseScope) {
decider.phaseEnded(phaseScope);
phaseScope.endingNow();
logger.info("""
{}Exhaustive Search phase ({}) ended: time spent ({}), best score ({}),\
{}Exhaustive Search phase ({}) ended: time spent ({}), environment mode ({}), best score ({}),\
move evaluation speed ({}/sec), step total ({}).""",
logIndentation,
phaseIndex,
phaseScope.calculateSolverTimeMillisSpentUpToNow(),
environmentMode.name(),
phaseScope.getBestScore().raw(),
phaseScope.getPhaseMoveEvaluationSpeed(),
phaseScope.getNextStepIndex());
Expand Down Expand Up @@ -141,17 +142,17 @@ public static class Builder<Solution_> extends AbstractPhaseBuilder<Solution_> {
private boolean assertWorkingSolutionScoreFromScratch = false;
private boolean assertExpectedWorkingSolutionScore = false;

public Builder(int phaseIndex, String logIndentation, PhaseTermination<Solution_> phaseTermination,
Comparator<ExhaustiveSearchNode<Solution_>> nodeComparator,
public Builder(int phaseIndex, EnvironmentMode environmentMode, String logIndentation,
PhaseTermination<Solution_> phaseTermination, Comparator<ExhaustiveSearchNode<Solution_>> nodeComparator,
AbstractExhaustiveSearchDecider<Solution_, ? extends Score<?>> decider) {
super(phaseIndex, logIndentation, phaseTermination);
super(phaseIndex, environmentMode, logIndentation, phaseTermination);
this.nodeComparator = nodeComparator;
this.decider = decider;
}

@Override
public Builder<Solution_> enableAssertions(EnvironmentMode environmentMode) {
super.enableAssertions(environmentMode);
public Builder<Solution_> enableAssertions() {
super.enableAssertions();
assertWorkingSolutionScoreFromScratch = environmentMode.isFullyAsserted();
assertExpectedWorkingSolutionScore = environmentMode.isIntrusivelyAsserted();
Comment thread
zepfred marked this conversation as resolved.
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,32 +70,33 @@ public ExhaustiveSearchPhase<Solution_> buildPhase(int phaseIndex, boolean lastI
var phaseTermination = buildPhaseTermination(phaseConfigPolicy, solverTermination);
var scoreBounderEnabled = exhaustiveSearchType.isScoreBounderEnabled();
var nodeExplorationType = getNodeExplorationType(exhaustiveSearchType, phaseConfig);
var environmentMode = resolveEnvironmentMode(phaseConfigPolicy);
AbstractExhaustiveSearchDecider<Solution_, ? extends Score<?>> decider;
if (isMixedModel) {
var basicVarEntitySelectorConfig = buildEntitySelectorConfig(phaseConfigPolicy, false);
var basicVarEntitySelector = EntitySelectorFactory.<Solution_> create(basicVarEntitySelectorConfig)
.buildEntitySelector(phaseConfigPolicy, SelectionCacheType.PHASE, SelectionOrder.ORIGINAL);
var basicVarDecider =
buildDecider(phaseConfigPolicy, basicVarEntitySelector, bestSolutionRecaller, phaseTermination,
scoreBounderEnabled, false);
buildDecider(phaseConfigPolicy, basicVarEntitySelector, bestSolutionRecaller, environmentMode,
phaseTermination, scoreBounderEnabled, false);
var listVarEntitySelectorConfig = buildEntitySelectorConfig(phaseConfigPolicy, true);
var listVarEntitySelector = EntitySelectorFactory.<Solution_> create(listVarEntitySelectorConfig)
.buildEntitySelector(phaseConfigPolicy, SelectionCacheType.PHASE, SelectionOrder.ORIGINAL);
var listVarDecider = buildDecider(phaseConfigPolicy, listVarEntitySelector, bestSolutionRecaller, phaseTermination,
scoreBounderEnabled, true);
var listVarDecider = buildDecider(phaseConfigPolicy, listVarEntitySelector, bestSolutionRecaller, environmentMode,
phaseTermination, scoreBounderEnabled, true);
decider = new MixedVariableExhaustiveSearchDecider<>(basicVarDecider, listVarDecider);
} else {
var isListVariable = solverConfigPolicy.getSolutionDescriptor().getListVariableDescriptor() != null;
var entitySelectorConfig = buildEntitySelectorConfig(phaseConfigPolicy, isListVariable);
var entitySelector =
EntitySelectorFactory.<Solution_> create(entitySelectorConfig)
.buildEntitySelector(phaseConfigPolicy, SelectionCacheType.PHASE, SelectionOrder.ORIGINAL);
decider = buildDecider(phaseConfigPolicy, entitySelector, bestSolutionRecaller, phaseTermination,
decider = buildDecider(phaseConfigPolicy, entitySelector, bestSolutionRecaller, environmentMode, phaseTermination,
scoreBounderEnabled, isListVariable);
}
return new DefaultExhaustiveSearchPhase.Builder<>(phaseIndex, solverConfigPolicy.getLogIndentation(), phaseTermination,
nodeExplorationType.buildNodeComparator(scoreBounderEnabled), decider)
.enableAssertions(phaseConfigPolicy.getEnvironmentMode()).build();
return new DefaultExhaustiveSearchPhase.Builder<>(phaseIndex, environmentMode, solverConfigPolicy.getLogIndentation(),
phaseTermination, nodeExplorationType.buildNodeComparator(scoreBounderEnabled), decider)
.enableAssertions().build();
}

private static NodeExplorationType getNodeExplorationType(ExhaustiveSearchType exhaustiveSearchType,
Expand Down Expand Up @@ -158,8 +159,8 @@ protected EntityDescriptor<Solution_> deduceEntityDescriptor(SolutionDescriptor<

private AbstractExhaustiveSearchDecider<Solution_, ? extends Score<?>> buildDecider(
HeuristicConfigPolicy<Solution_> configPolicy, EntitySelector<Solution_> sourceEntitySelector,
BestSolutionRecaller<Solution_> bestSolutionRecaller, PhaseTermination<Solution_> termination,
boolean scoreBounderEnabled, boolean isListVariable) {
BestSolutionRecaller<Solution_> bestSolutionRecaller, EnvironmentMode environmentMode,
PhaseTermination<Solution_> termination, boolean scoreBounderEnabled, boolean isListVariable) {
var manualEntityMimicRecorder = new ManualEntityMimicRecorder<>(sourceEntitySelector);
var entityClassName = sourceEntitySelector.getEntityDescriptor().getEntityClass().getName();
var mimicSelectorId = ConfigUtils.addRandomSuffix(entityClassName, configPolicy.getRandom().factoryUsage());
Expand Down Expand Up @@ -200,13 +201,7 @@ protected EntityDescriptor<Solution_> deduceEntityDescriptor(SolutionDescriptor<
new MoveSelectorBasedMoveRepository<>(moveSelector), scoreBounderEnabled, scoreBounder);

}
EnvironmentMode environmentMode = configPolicy.getEnvironmentMode();
if (environmentMode.isFullyAsserted()) {
decider.setAssertMoveScoreFromScratch(true);
}
if (environmentMode.isIntrusivelyAsserted()) {
decider.setAssertExpectedUndoMoveScore(true);
}
decider.enableAssertions(environmentMode);
return decider;
}

Expand Down
Loading
Loading