/* * Copyright 2011 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.optaplanner.core.impl.localsearch; import org.optaplanner.core.api.domain.solution.PlanningSolution; import org.optaplanner.core.impl.heuristic.move.Move; import org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider; import org.optaplanner.core.impl.localsearch.event.LocalSearchPhaseLifecycleListener; import org.optaplanner.core.impl.localsearch.scope.LocalSearchPhaseScope; import org.optaplanner.core.impl.localsearch.scope.LocalSearchStepScope; import org.optaplanner.core.impl.phase.AbstractPhase; import org.optaplanner.core.impl.solver.recaller.BestSolutionRecaller; import org.optaplanner.core.impl.solver.scope.DefaultSolverScope; import org.optaplanner.core.impl.solver.termination.Termination; /** * Default implementation of {@link LocalSearchPhase}. * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation */ public class DefaultLocalSearchPhase<Solution_> extends AbstractPhase<Solution_> implements LocalSearchPhase<Solution_>, LocalSearchPhaseLifecycleListener<Solution_> { protected LocalSearchDecider<Solution_> decider; public DefaultLocalSearchPhase(int phaseIndex, String logIndentation, BestSolutionRecaller<Solution_> bestSolutionRecaller, Termination termination) { super(phaseIndex, logIndentation, bestSolutionRecaller, termination); } public LocalSearchDecider<Solution_> getDecider() { return decider; } public void setDecider(LocalSearchDecider<Solution_> decider) { this.decider = decider; } @Override public String getPhaseTypeString() { return "Local Search"; } // ************************************************************************ // Worker methods // ************************************************************************ @Override public void solve(DefaultSolverScope<Solution_> solverScope) { LocalSearchPhaseScope<Solution_> phaseScope = new LocalSearchPhaseScope<>(solverScope); phaseStarted(phaseScope); while (!termination.isPhaseTerminated(phaseScope)) { LocalSearchStepScope<Solution_> stepScope = new LocalSearchStepScope<>(phaseScope); stepScope.setTimeGradient(termination.calculatePhaseTimeGradient(phaseScope)); stepStarted(stepScope); decider.decideNextStep(stepScope); if (stepScope.getStep() == null) { if (termination.isPhaseTerminated(phaseScope)) { logger.trace("{} Step index ({}), time spent ({}) terminated without picking a nextStep.", logIndentation, stepScope.getStepIndex(), stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow()); } else if (stepScope.getSelectedMoveCount() == 0L) { 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() + ")."); } // Although stepStarted has been called, stepEnded is not called for this step break; } doStep(stepScope); stepEnded(stepScope); phaseScope.setLastCompletedStepScope(stepScope); } phaseEnded(phaseScope); } protected void doStep(LocalSearchStepScope<Solution_> stepScope) { Move<Solution_> nextStep = stepScope.getStep(); nextStep.doMove(stepScope.getScoreDirector()); predictWorkingStepScore(stepScope, nextStep); bestSolutionRecaller.processWorkingSolutionDuringStep(stepScope); } @Override public void solvingStarted(DefaultSolverScope<Solution_> solverScope) { super.solvingStarted(solverScope); decider.solvingStarted(solverScope); } @Override public void phaseStarted(LocalSearchPhaseScope<Solution_> phaseScope) { super.phaseStarted(phaseScope); decider.phaseStarted(phaseScope); // TODO maybe this restriction should be lifted to allow LocalSearch to initialize a solution too? assertWorkingSolutionInitialized(phaseScope); } @Override public void stepStarted(LocalSearchStepScope<Solution_> stepScope) { super.stepStarted(stepScope); decider.stepStarted(stepScope); } @Override public void stepEnded(LocalSearchStepScope<Solution_> stepScope) { super.stepEnded(stepScope); decider.stepEnded(stepScope); LocalSearchPhaseScope phaseScope = stepScope.getPhaseScope(); if (logger.isDebugEnabled()) { logger.debug("{} LS step ({}), time spent ({}), score ({}), {} best score ({})," + " accepted/selected move count ({}/{}), picked move ({}).", logIndentation, stepScope.getStepIndex(), phaseScope.calculateSolverTimeMillisSpentUpToNow(), stepScope.getScore(), (stepScope.getBestScoreImproved() ? "new" : " "), phaseScope.getBestScore(), stepScope.getAcceptedMoveCount(), stepScope.getSelectedMoveCount(), stepScope.getStepString()); } } @Override public void phaseEnded(LocalSearchPhaseScope<Solution_> phaseScope) { super.phaseEnded(phaseScope); decider.phaseEnded(phaseScope); phaseScope.endingNow(); logger.info("{}Local Search phase ({}) ended: time spent ({}), best score ({})," + " score calculation speed ({}/sec), step total ({}).", logIndentation, phaseIndex, phaseScope.calculateSolverTimeMillisSpentUpToNow(), phaseScope.getBestScore(), phaseScope.getPhaseScoreCalculationSpeed(), phaseScope.getNextStepIndex()); } @Override public void solvingEnded(DefaultSolverScope<Solution_> solverScope) { super.solvingEnded(solverScope); decider.solvingEnded(solverScope); } }