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 @@ -159,7 +159,8 @@ protected void doMove(ConstructionHeuristicMoveScope<Solution_> moveScope) {
}
if (isLoggingEnabled()) {
logger.trace("{} Move index ({}), score ({}), move ({}).",
logIndentation, moveScope.getMoveIndex(), moveScope.getScore().raw(), moveScope.getMove());
logIndentation, moveScope.getMoveIndex(),
(score != null) ? moveScope.getScore().raw() : "invalid", moveScope.getMove());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public void addMove(ConstructionHeuristicMoveScope<Solution_> moveScope) {
moveScope.getStepScope().getPhaseScope()
.addMoveEvaluationCount(moveScope.getMove(), 1L);
checkPickEarly(moveScope);
if (maxScoreMoveScope == null || moveScope.getScore().compareTo(maxScoreMoveScope.getScore()) > 0) {
if (maxScoreMoveScope == null || maxScoreMoveScope.getScore() == null ||
(moveScope.getScore() != null && moveScope.getScore().compareTo(maxScoreMoveScope.getScore()) > 0)) {
maxScoreMoveScope = moveScope;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,12 @@ protected <Score_ extends Score<Score_>> void doMove(LocalSearchMoveScope<Soluti
moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore(),
SolverLifecyclePoint.of(moveScope));
}
logger.trace("{} Move index ({}), score ({}), accepted ({}), move ({}).",
logIndentation, moveScope.getMoveIndex(), moveScope.getScore().raw(), moveScope.getAccepted(),
moveScope.getMove());
if (logger.isTraceEnabled()) {
logger.trace("{} Move index ({}), score ({}), accepted ({}), move ({}).",
logIndentation, moveScope.getMoveIndex(),
(moveScope.getScore() != null) ? moveScope.getScore().raw() : "invalid",
moveScope.getAccepted(), moveScope.getMove());
}
}

protected void pickMove(LocalSearchStepScope<Solution_> stepScope) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ai.timefold.solver.core.impl.localsearch.decider.acceptor;

import ai.timefold.solver.core.impl.localsearch.event.LocalSearchPhaseLifecycleListenerAdapter;
import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -19,4 +20,13 @@ public abstract class AbstractAcceptor<Solution_> extends LocalSearchPhaseLifecy
// Worker methods
// ************************************************************************

public final boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
if (moveScope.getScore() == null) {
return false;
}
return isConsistentSolutionAccepted(moveScope);
}

public abstract boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope);

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ public void stepStarted(LocalSearchStepScope<Solution_> stepScope) {
}

@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
if (moveScope.getScore().isInvalid()) {
return false;
}
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
for (Acceptor<Solution_> acceptor : acceptorList) {
boolean accepted = acceptor.isAccepted(moveScope);
if (!accepted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ public void phaseEnded(LocalSearchPhaseScope<Solution_> phaseScope) {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope moveScope) {
if (moveScope.getScore().isInvalid()) {
return false;
}
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope moveScope) {
var moveScore = moveScope.getScore().raw();
if (moveScore.compareTo(currentWaterLevel) >= 0) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@

public class HillClimbingAcceptor<Solution_> extends AbstractAcceptor<Solution_> {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
InnerScore moveScore = moveScope.getScore();
if (moveScore.isInvalid()) {
return false;
}
InnerScore lastStepScore = moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore();
return moveScore.compareTo(lastStepScore) >= 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,10 @@ private void validate() {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
// The acceptance and replacement strategies are based on the work:
// Diversified Late Acceptance Search by M. Namazi, C. Sanderson, M. A. H. Newton, M. M. A. Polash, and A. Sattar
var moveScore = moveScope.getScore();
if (moveScore.isInvalid()) {
return false;
}
var current = (InnerScore) moveScope.getStepScope().getPhaseScope()
.getLastCompletedStepScope().getScore();
var previous = current;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,8 @@ private void validate() {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
var moveScore = (InnerScore) moveScope.getScore();
if (moveScore.isInvalid()) {
return false;
}
var lateScore = scoreBuffer.getCurrent();
if (moveScore.compareTo(lateScore) >= 0) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ public void phaseEnded(LocalSearchPhaseScope<Solution_> phaseScope) {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
var phaseScope = moveScope.getStepScope().getPhaseScope();
// Guaranteed local search; no need for InnerScore.
Score lastStepScore = phaseScope.getLastCompletedStepScope().getScore().raw();
Score moveScore = moveScope.getScore().raw();
var thisStepInvalid = moveScope.getScore().isInvalid();
if (thisStepInvalid) {
return false;
}
if (moveScore.compareTo(lastStepScore) >= 0) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ public void phaseStarted(LocalSearchPhaseScope<Solution_> phaseScope) {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
InnerScore lastStepScore = moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore();
InnerScore moveScore = moveScope.getScore();
if (moveScore.isInvalid()) {
return false;
}
if (moveScore.compareTo(lastStepScore) >= 0) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,7 @@ private static IllegalStateException createHashcodeStabilityViolationException(O
}

@Override
public boolean isAccepted(LocalSearchMoveScope<Solution_> moveScope) {
var thisStepInvalid = moveScope.getScore().isInvalid();
if (thisStepInvalid) {
return false;
}
public boolean isConsistentSolutionAccepted(LocalSearchMoveScope<Solution_> moveScope) {
var maximumTabuStepIndex = locateMaximumTabuStepIndex(moveScope);
if (maximumTabuStepIndex < 0) {
// The move isn't tabu at all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void stepStarted(LocalSearchStepScope<Solution_> stepScope) {
@Override
public void addMove(LocalSearchMoveScope<Solution_> moveScope) {
var accepted = moveScope.getAccepted() != null && moveScope.getAccepted();
if (finalistIsAccepted && !accepted) {
if ((finalistIsAccepted && !accepted) || moveScope.getScore() == null) {
return;
}
if (accepted && !finalistIsAccepted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ public final void execute(Move<Solution_> move, boolean guaranteeFreshScore) {
}
}

public final InnerScore<Score_> executeTemporary(Move<Solution_> move) {
public @Nullable final InnerScore<Score_> executeTemporary(Move<Solution_> move) {
var ephemeralMoveDirector = ephemeral();
ephemeralMoveDirector.execute(move);
var score = backingScoreDirector.calculateScore();
Expand All @@ -426,7 +426,7 @@ public final InnerScore<Score_> executeTemporary(Move<Solution_> move) {
}

public @Nullable <Result_> Result_ executeTemporary(Move<Solution_> move,
TemporaryMovePostprocessor<Solution_, Score_, @Nullable Result_> postprocessor) {
TemporaryMovePostprocessor<Solution_, @Nullable Score_, @Nullable Result_> postprocessor) {
try (var ephemeralMoveDirector = ephemeral()) {
ephemeralMoveDirector.execute(move);
var score = backingScoreDirector.calculateScore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private void doStep(CustomStepScope<Solution_> stepScope, PhaseCommand<Solution_
() -> phaseTermination.isPhaseTerminated(stepScope.getPhaseScope()));
customPhaseCommand.changeWorkingSolution(commandContext);
calculateWorkingStepScore(stepScope, customPhaseCommand);
if (stepScope.getScore().isInvalid()) {
if (stepScope.getScore() == null) {
throw new IllegalStateException("The custom phase command (%s) resulted in an inconsistent solution."
.formatted(customPhaseCommand));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,12 @@ public NeighborhoodNotifier<Solution_> getNeighborhoodNotifier() {
public abstract InnerScore<Score_> innerCalculateScore();

@Override
@Nullable
public final InnerScore<Score_> calculateScore() {
if (lastVariableUpdateSuccessful) {
return innerCalculateScore();
} else {
var invalidScore = InnerScore.invalid(getScoreDefinition().getZeroScore());
getSolutionDescriptor().setScore(workingSolution, invalidScore.raw());
return invalidScore;
return null;
}
}

Expand Down Expand Up @@ -432,12 +431,13 @@ public void executeMove(Move<Solution_> move) {
}

@Override
@Nullable
public InnerScore<Score_> executeTemporaryMove(Move<Solution_> move, @Nullable Consumer<SolutionView<Solution_>> consumer,
boolean assertMoveScoreFromScratch) {
if (solutionTracker != null) {
solutionTracker.setBeforeMoveSolution(workingSolution);
}
var result = moveDirector.executeTemporary(move, (score, undoMove) -> {
return moveDirector.executeTemporary(move, (score, undoMove) -> {
if (solutionTracker != null) {
solutionTracker.setAfterMoveSolution(workingSolution);
}
Expand All @@ -449,7 +449,6 @@ public InnerScore<Score_> executeTemporaryMove(Move<Solution_> move, @Nullable C
}
return score;
});
return Objects.requireNonNull(result);
}

@Override
Expand Down Expand Up @@ -809,7 +808,7 @@ private void assertScoreFromScratch(InnerScore<Score_> innerScore, Object comple
.buildDerived()) {
uncorruptedScoreDirector.setWorkingSolution(workingSolution);
var uncorruptedInnerScore = uncorruptedScoreDirector.calculateScore();
if (!innerScore.equals(uncorruptedInnerScore)) {
if (!Objects.equals(innerScore, uncorruptedInnerScore)) {
var corruptionAnalyzer = new CorruptionAnalyzer<>(this);
var scoreCorruptionAnalysis = corruptionAnalyzer.analyzeScore(uncorruptedScoreDirector, predicted);
var shadowVariableAnalysis = corruptionAnalyzer.analyzeShadowVariables(predicted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ public static <Score_ extends Score<Score_>> InnerScore<Score_> withUnassignedCo
return new InnerScore<>(score, unassignedCount);
}

public static <Score_ extends Score<Score_>> InnerScore<Score_> invalid(Score_ zeroScore) {
return new InnerScore<>(zeroScore, Integer.MAX_VALUE);
}

public InnerScore {
Objects.requireNonNull(raw);
if (unassignedCount < 0) {
Expand All @@ -50,10 +46,6 @@ public boolean isFullyAssigned() {
return unassignedCount == 0;
}

public boolean isInvalid() {
return unassignedCount == Integer.MAX_VALUE;
}

@Override
public int compareTo(InnerScore<Score_> other) {
var uninitializedCountComparison = Integer.compare(unassignedCount, other.unassignedCount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ public interface InnerScoreDirector<Solution_, Score_ extends Score<Score_>>
/**
* Calculates the {@link Score} and updates the {@link PlanningSolution working solution} accordingly.
*
* @return never null, the {@link Score} of the {@link PlanningSolution working solution}
* @return the {@link Score} of the {@link PlanningSolution working solution}, null if the working solution is
* inconsistent
*/
@Nullable
InnerScore<Score_> calculateScore();

/**
Expand Down Expand Up @@ -125,12 +127,14 @@ public interface InnerScoreDirector<Solution_, Score_ extends Score<Score_>>
* @param assertMoveScoreFromScratch true will hurt performance
* @return never null
*/
@Nullable
InnerScore<Score_> executeTemporaryMove(Move<Solution_> move, @Nullable Consumer<SolutionView<Solution_>> consumer,
boolean assertMoveScoreFromScratch);

/**
* As defined by {@link #executeTemporaryMove(Move, Consumer, boolean)}, but with no consumer.
*/
@Nullable
default InnerScore<Score_> executeTemporaryMove(Move<Solution_> move, boolean assertMoveScoreFromScratch) {
return executeTemporaryMove(move, null, assertMoveScoreFromScratch);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private <Result_> Result_ callScoreDirector(String feature, Solution_ solution,
}
if (solutionUpdatePolicy.isScoreUpdateEnabled()) {
var score = scoreDirector.calculateScore();
if (score.isInvalid()) {
if (score == null) {
var inconsistentEntities = scoreDirector.computeInconsistentEntities();
throw new InconsistentSolutionException(feature, nonNullSolution, inconsistentEntities);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public void solvingStarted(SolverScope<Solution_> solverScope) {
var scoreDirector = solverScope.getScoreDirector();
@SuppressWarnings("rawtypes")
InnerScore innerScore = scoreDirector.calculateScore();
if (innerScore.isInvalid()) {
if (innerScore == null) {
LOGGER.warn("The initial solution passed to the solver is inconsistent. Unassigning involved entities.");
scoreDirector.unassignInconsistentEntities();
innerScore = scoreDirector.calculateScore();
if (innerScore.isInvalid()) {
if (innerScore == null) {
// If there were a fixed dependency loop, the shadow variable session would fail fast before here
throw new IllegalStateException(
"Impossible state: The initial solution passed to the solver is inconsistent even after unassigning involved entities.");
Expand Down