All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.solver.Solver Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2024, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.solver;

import org.chocosolver.memory.IEnvironment;
import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.InvalidSolutionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.learn.AbstractEventObserver;
import org.chocosolver.solver.objective.IBoundsManager;
import org.chocosolver.solver.objective.IObjectiveManager;
import org.chocosolver.solver.objective.ObjectiveFactory;
import org.chocosolver.solver.propagation.PropagationEngine;
import org.chocosolver.solver.search.SearchState;
import org.chocosolver.solver.search.limits.ICounter;
import org.chocosolver.solver.search.loop.Reporting;
import org.chocosolver.solver.search.loop.learn.Learn;
import org.chocosolver.solver.search.loop.learn.LearnNothing;
import org.chocosolver.solver.search.loop.monitors.IMonitorSolution;
import org.chocosolver.solver.search.loop.monitors.ISearchMonitor;
import org.chocosolver.solver.search.loop.monitors.SearchMonitorList;
import org.chocosolver.solver.search.loop.move.Move;
import org.chocosolver.solver.search.loop.move.MoveBinaryDFS;
import org.chocosolver.solver.search.loop.move.MoveSeq;
import org.chocosolver.solver.search.loop.propagate.Propagate;
import org.chocosolver.solver.search.measure.IMeasures;
import org.chocosolver.solver.search.measure.MeasuresRecorder;
import org.chocosolver.solver.search.restart.AbstractRestart;
import org.chocosolver.solver.search.strategy.BlackBoxConfigurator;
import org.chocosolver.solver.search.strategy.Search;
import org.chocosolver.solver.search.strategy.decision.Decision;
import org.chocosolver.solver.search.strategy.decision.DecisionPath;
import org.chocosolver.solver.search.strategy.strategy.AbstractStrategy;
import org.chocosolver.solver.search.strategy.strategy.StrategiesSequencer;
import org.chocosolver.solver.search.strategy.strategy.WarmStart;
import org.chocosolver.solver.trace.IOutputFactory;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.Task;
import org.chocosolver.solver.variables.Variable;
import org.chocosolver.util.ESat;
import org.chocosolver.util.criteria.Criterion;
import org.chocosolver.util.iterators.DisposableValueIterator;
import org.chocosolver.util.logger.ANSILogger;
import org.chocosolver.util.logger.Logger;

import java.util.*;
import java.util.stream.Stream;

import static org.chocosolver.solver.Solver.Action.*;
import static org.chocosolver.solver.constraints.Constraint.Status.FREE;
import static org.chocosolver.util.ESat.*;

/**
 * This class is inspired from :
 * 
 * Inspired from "Unifying search algorithms for CSP" N. Jussien and O. Lhomme, Technical report 02-3-INFO, EMN
 * 
 * 

* It declares a search loop made of three components: *

    *
  • * Propagate: it aims at propagating information throughout the constraint network when a decision is made, *
  • *
  • * Learn: it aims at ensuring that the search mechanism will avoid (as much as possible) to get back to states that have been explored and proved to be solution-less, *
  • *
  • * Move: aims at, unlike other ones, not pruning the search space but rather exploring it. *
  • *

    *

*

* Created by cprudhom on 01/09/15. * Project: choco. * * @author Charles Prud'homme * @since 01/09/15. */ public class Solver implements ISolver, IMeasures, IOutputFactory { /** * Define the possible actions of SearchLoop */ public enum Action { /** * Initialization step */ initialize, /** * propagation step */ propagate, /** * fixpoint step */ fixpoint, /** * extension step */ extend, /** * validation step */ validate, /** * reparation step */ repair } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// PRIVATE FIELDS ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * The learning component of this search loop */ protected Learn L; /** * The moving component of this search loop */ protected Move M; /** * The declaring model */ protected Model mModel; /** * The objective manager declare */ @SuppressWarnings({"WeakerAccess", "rawtypes"}) protected IObjectiveManager objectivemanager; /** * The next action to execute in the search loop */ protected Action action; /** * The measure recorder to keep up to date */ @SuppressWarnings("WeakerAccess") protected MeasuresRecorder mMeasures; /** * The current decision */ @SuppressWarnings("WeakerAccess") protected DecisionPath dpath; /** * Index of the initial world, before initialization. * May be different from 0 if some external backups have been made. */ private int rootWorldIndex = 0; /** * Index of the world where the search starts, after initialization. */ private int searchWorldIndex = 0; /** * List of stopping criteria. * When at least one is satisfied, the search loop ends. */ protected List criteria; /** * Indicates if the default search loop is in use (set to true in that case). */ private boolean defaultSearch = false; /** * Indicates if a complementary search strategy should be added (set to true in that case). */ private boolean completeSearch = false; /** * An events observer */ private AbstractEventObserver eventObserver; /** * List of search monitors attached to this search loop */ @SuppressWarnings("WeakerAccess") protected SearchMonitorList searchMonitors; /** * The propagation engine to use */ protected PropagationEngine engine; /** * Internal unique contradiction exception, used on propagation failures */ protected final ContradictionException exception; /** * Problem feasbility: * - UNDEFINED if unknown, * - TRUE if satisfiable, * - FALSE if unsatisfiable */ protected ESat feasible = ESat.UNDEFINED; /** * Counter that indicates how many world should be rolled back when backtracking */ private int jumpTo; /** * Set to true to stop the search loop **/ protected boolean stop; /** * Set to true when no more reparation can be achieved, ie entire search tree explored. */ private boolean canBeRepaired = true; /** * The restarting strategy */ private AbstractRestart restarter; /** * This object is accessible lazily */ private Solution lastSol = null; /** * Store hints on partial solution, to better start the search */ private WarmStart warmStart = null; /** * Default logger */ private Logger logger = new ANSILogger(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// CONSTRUCTOR ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Create a resolver based for the model aModel. * * @param aModel the target model */ protected Solver(Model aModel) { mModel = aModel; engine = new PropagationEngine(mModel); exception = new ContradictionException(); eventObserver = AbstractEventObserver.SILENT_OBSERVER; objectivemanager = ObjectiveFactory.SAT(); dpath = new DecisionPath(aModel.getEnvironment()); action = initialize; mMeasures = new MeasuresRecorder(mModel.getName()); criteria = new ArrayList<>(); mMeasures.setSearchState(SearchState.NEW); mMeasures.setBoundsManager(objectivemanager); searchMonitors = new SearchMonitorList(); setMove(new MoveBinaryDFS()); setNoLearning(); restarter = AbstractRestart.NO_RESTART; } public void throwsException(ICause c, Variable v, String s) throws ContradictionException { throw exception.set(c, v, s); } public ContradictionException getContradictionException() { return exception; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// SEARCH LOOP ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Executes the resolver as it is configured. *

* Default configuration: * - SATISFACTION : Computes a feasible solution. Use while(solve()) to enumerate all solutions. * - OPTIMISATION : Computes a feasible solution, wrt to the objective defined. Use while(solve()) to find the optimal solution. * Indeed, each new solution improves the objective. If no new solution is found (and no stop criterion encountered), * the last one is guaranteed to be the optimal one. * * @return if at least one new solution has been found. */ public boolean solve() { mMeasures.setSearchState(SearchState.RUNNING); // prepare boolean satPb = getModel().getResolutionPolicy() == ResolutionPolicy.SATISFACTION; if (getModel().getObjective() == null && !satPb) { throw new SolverException("No objective variable has been defined whereas policy implies optimization"); } stop = !canBeRepaired; if (action == initialize) { searchMonitors.beforeInitialize(); boolean ok = initialize(); searchMonitors.afterInitialize(ok); } // solve boolean newSolutionFound = searchLoop(); // close searchMonitors.beforeClose(); closeSearch(); searchMonitors.afterClose(); // restoration return newSolutionFound; } /** * Executes the search loop * * @return true if ends on a solution, false otherwise */ @SuppressWarnings("WeakerAccess") public boolean searchLoop() { boolean solution = false; boolean left = true; Thread th = Thread.currentThread(); while (!stop) { stop = isStopCriterionMet(); if (stop || th.isInterrupted()) { if (stop) { mMeasures.setSearchState(SearchState.STOPPED); } else { mMeasures.setSearchState(SearchState.KILLED); } } switch (action) { case initialize: throw new UnsupportedOperationException("should not initialize during search loop"); case propagate: propagate(left); break; case fixpoint: fixpoint(); break; case extend: left = true; extend(); break; case repair: left = false; repair(); break; case validate: stop = solution = validate(); break; default: throw new SolverException("Invalid Solver loop action " + action); } } return solution; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// MAIN METHODS ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Preparation of the search: * - start time recording, * - store root world * - push a back up world, * - run the initial propagation, * - initialize the Move and the search strategy */ protected boolean initialize() { boolean ok = true; if (mModel.getSettings().checkDeclaredConstraints()) { //noinspection unchecked Set instances = (Set) mModel.getHook("cinstances"); if (instances != null) { Optional undeclared = instances .stream() .filter(c -> (c.getStatus() == FREE)) .findFirst(); if (undeclared.isPresent()) { logger.white().println( "At least one constraint is free, i.e., neither posted or reified. )."); instances .stream() .filter(c -> c.getStatus() == FREE) .limit(mModel.getSettings().printAllUndeclaredConstraints() ? Integer.MAX_VALUE : 1) .forEach(c -> logger.white().printf(String.format("%s is free\n", c))); } } } engine.initialize(); getMeasures().setReadingTimeCount(System.nanoTime() - mModel.getCreationTime()); // end note mMeasures.startStopwatch(); rootWorldIndex = mModel.getEnvironment().getWorldIndex(); // Indicates which decision was previously applied before selecting the move. // Always sets to ROOT for the first move M.setTopDecisionPosition(0); mModel.getEnvironment().worldPush(); // store state before initial propagation; w = 0 -> 1 try { if (mModel.getHook(Model.TASK_SET_HOOK_NAME) != null) { //noinspection unchecked ArrayList tset = (ArrayList) mModel.getHook(Model.TASK_SET_HOOK_NAME); for (int i = 0; i < tset.size(); i++) { tset.get(i).ensureBoundConsistency(); } } mMeasures.incFixpointCount(); doPropagate(); action = extend; mModel.getEnvironment().worldPush(); // store state after initial propagation; w = 1 -> 2 searchWorldIndex = mModel.getEnvironment().getWorldIndex(); // w = 2 mModel.getEnvironment().worldPush(); // store another time for restart purpose: w = 2 -> 3 } catch (ContradictionException ce) { engine.flush(); mMeasures.incFailCount(); searchMonitors.onContradiction(ce); L.record(this); L.forget(this); mModel.getEnvironment().worldPop(); stop = true; ok = false; } // call to HeuristicVal.update(Action.initial_propagation) if (M.getChildMoves().size() <= 1 && M.getStrategy() == null) { if (getModel().getSettings().warnUser()) { logger.white().println("No search strategies defined."); logger.white().println("Set to default ones."); } defaultSearch = true; mModel.getSettings().makeDefaultSearch(mModel); } preprocessing(getModel().getSettings().getTimeLimitForPreprocessing()); if (completeSearch && !defaultSearch) { BlackBoxConfigurator bb = BlackBoxConfigurator.init(); bb.complete(mModel, M.getStrategy()); } if (warmStart != null) { AbstractStrategy declared = M.getStrategy(); warmStart.setStrategy(declared); setSearch(warmStart); } restarter.init(); if (!M.init()) { // the initialisation of the Move and strategy can detect inconsistency mModel.getEnvironment().worldPop(); feasible = FALSE; engine.flush(); getMeasures().incFailCount(); ok = stop = true; } criteria.stream().filter(c -> c instanceof ICounter).forEach(c -> ((ICounter) c).init()); return ok; } /** * This method is called after the initial propagation and before the search loop starts. * It sequentially applies Arc Consistency on every combination of (variable, value). * If a value is not supported by any other variable, it is removed from the domain of the variable. * The method ends when the time limit is reached or when all combination have been checked. * @implSpec A first propagation must have been done before calling this method. */ public void preprocessing(long timeLimitInMS) { if(!getEngine().isInitialized()){ throw new SolverException("A call to solver.propagate() must be done before calling solver.preprocessing()"); } if (timeLimitInMS > 0 && getModel().getSettings().warnUser()) { logger.white().printf("Running preprocessing step (%dms).\n", timeLimitInMS); } long tl = System.currentTimeMillis() + timeLimitInMS; IntVar[] ivars = mModel.retrieveIntVars(true); loop: for (int i = 0; i < ivars.length; i++) { IntVar v = ivars[i]; if (!v.isInstantiated()) { // if the variable is not instantiated DisposableValueIterator it = v.getValueIterator(true); while (it.hasNext()) { if (System.currentTimeMillis() > tl) { break loop; } int a = it.next(); if(!hasSupport(v, a)){ try { v.removeValue(a, Cause.Null); if (getModel().getSettings().warnUser()) { logger.white().printf("Preprocessing removed value %d from %s\n", a, v.getName()); } } catch (ContradictionException e) { throw new SolverException("Preprocessing failed"); } } } it.dispose(); } } } private boolean hasSupport(IntVar var, int val) { mModel.getEnvironment().worldPush(); try { var.instantiateTo(val, Cause.Null); mModel.getSolver().getEngine().propagate(); return true; } catch (ContradictionException e) { mModel.getSolver().getEngine().flush(); return false; } finally { mModel.getEnvironment().worldPop(); } } /** * Basic propagation: *

    *
  • First, prepare the decision (to ensure good behavior of the * {@link org.chocosolver.solver.search.loop.move.Move#repair(Solver)} call)
  • *
  • then, a first propagation ensures that, if learning is on, * the unit no-good learnt on failure filters, *
  • the cut is posted before applying the decision to ensure good nogood, * and a second propagation ensures the cut is taken into account
  • *
  • the decision is applied (if learning is on and the decision is refuted, * it is bypassed by the learnt unit nogood),
  • *
  • finally, a fix point is reached.
  • *
* * @throws ContradictionException if failure occurs during propagation */ private void doPropagate() throws ContradictionException { //WARNING: keep the order as is (read javadoc for more details) dpath.buildNext(); objectivemanager.postDynamicCut(); engine.propagate(); dpath.apply(); engine.propagate(); } /** * Search loop propagation phase. This needs to be distinguished from {@link #propagate()} * * @param left true if we are branching on the left false otherwise */ protected void propagate(boolean left) { searchMonitors.beforeDownBranch(left); try { mMeasures.incFixpointCount(); doPropagate(); action = extend; } catch (ContradictionException ce) { engine.flush(); mMeasures.incFailCount(); jumpTo = 1; action = repair; searchMonitors.onContradiction(ce); } searchMonitors.afterDownBranch(left); } private void fixpoint() { try { mMeasures.incFixpointCount(); objectivemanager.postDynamicCut(); engine.propagate(); action = propagate; } catch (ContradictionException ce) { engine.flush(); // mMeasures.incFailCount(); jumpTo = 1; action = repair; searchMonitors.onContradiction(ce); } } /** * Search loop extend phase */ protected void extend() { searchMonitors.beforeOpenNode(); mMeasures.incNodeCount(); action = propagate; if (restarter.mustRestart(this)) { this.restart(); } else if (!M.extend(this)) { action = validate; } searchMonitors.afterOpenNode(); } /** * Search loop repair phase */ protected void repair() { if (L.record(this)) { // this is done before the reparation, // since restart is a move which can stop the search if the cut fails action = fixpoint; } else { // this is done before the reparation, // since restart is a move which can stop the search if the cut fails action = propagate; } searchMonitors.beforeUpBranch(); if (restarter.mustRestart(this)) { canBeRepaired = true; this.restart(); } else { canBeRepaired = M.repair(this); } searchMonitors.afterUpBranch(); if (!canBeRepaired) { stop = true; } else { L.forget(this); } } /** * Search loop validate phase * * @return true if a solution is found */ private boolean validate() { if (!getModel().getSettings().checkModel(this)) { throw new InvalidSolutionException("The current solution does not satisfy the checker." + "Either (a) the search strategy is not complete or " + "(b) the model is not constrained enough or " + "(c) a constraint's checker (\"isSatisfied()\") is not correct or " + "(d) some constraints' filtering algorithm (\"propagate(...)\") is not correct.\n" + Reporting.fullReport(mModel), mModel); } feasible = TRUE; mMeasures.incSolutionCount(); if (mModel.getResolutionPolicy() == ResolutionPolicy.SATISFACTION && mMeasures.getSolutionCount() == 1) { mMeasures.updateTimeToBestSolution(); } else if (mModel.getResolutionPolicy() != ResolutionPolicy.SATISFACTION) { boolean bestSolutionHasBeenUpdated = objectivemanager.updateBestSolution(); if (bestSolutionHasBeenUpdated) { mMeasures.updateTimeToBestSolution(); } } searchMonitors.onSolution(); jumpTo = 1; action = repair; return true; } /** * Close the search: * - set satisfaction * - update statistics */ private void closeSearch() { if (mMeasures.getSearchState() == SearchState.RUNNING) { mMeasures.setSearchState(SearchState.TERMINATED); } feasible = FALSE; if (mMeasures.getSolutionCount() > 0) { feasible = TRUE; if (objectivemanager.isOptimization()) { mMeasures.setObjectiveOptimal(!isStopCriterionMet()); } } else if (isStopCriterionMet()) { mMeasures.setObjectiveOptimal(false); feasible = UNDEFINED; } } /** *

* Resetting a solver to the state just before running the last resolution instruction. * That is, {@link Learn}, {@link Move} and {@link Search} are kept as declared. * {@link ISearchMonitor} are also kept plugged to the search loop. *

*

* For hard reset, see {@link #hardReset()}. *

* In details, calling this method will: *
    *
  • backtrack to {@link #rootWorldIndex}
  • *
  • set {@link #searchWorldIndex} to 0
  • *
  • set {@link #action} to {@link Action#initialize}
  • *
  • reset {@link #mMeasures}
  • *
  • flush {@link #engine}
  • *
  • synchronize {@link #dpath} to erase out-dated decisions, presumably all of them
  • *
  • reset bounds of {@link #objectivemanager} (calling {@link IObjectiveManager#resetBestBounds()}
  • *
  • remove all stop criteria {@link #removeAllStopCriteria()}
  • *
  • set {@link #feasible} to {@link ESat#UNDEFINED}
  • *
* * @see #hardReset() */ public void reset() { if (rootWorldIndex > -1) { mModel.getEnvironment().worldPopUntil(rootWorldIndex); } searchWorldIndex = 0; action = initialize; mMeasures.reset(); engine.reset(); dpath.synchronize(); objectivemanager.resetBestBounds(); removeAllStopCriteria(); feasible = UNDEFINED; jumpTo = 0; stop = false; canBeRepaired = true; } /** *

* Resetting a solver to its creation state. *

* *

* For soft reset, see {@link #reset()}. *

*

* In details, calling this method will, first call {@link #reset()} and then: *

    *
  • replace {@link #M} by {@link MoveBinaryDFS}
  • *
  • call {@link Solver#setNoLearning()}
  • *
  • remove warm start hints
  • *
  • clear {@link #searchMonitors}, that forget any declared one
  • *
  • call {@link Model#removeMinisat()}
  • *
*

* * @see #reset() */ public void hardReset() { reset(); this.M.removeStrategy(); setMove(new MoveBinaryDFS()); setNoLearning(); //no need to unplug, done by searchMonitors.reset() this.lastSol = null; if (this.warmStart != null) { this.warmStart.clearHints(); this.warmStart = null; } searchMonitors.reset(); defaultSearch = false; completeSearch = false; mModel.removeMinisat(); } /** * Propagates constraints and related events through the constraint network until a fix point is find, * or a contradiction is detected. * * @throws ContradictionException inconsistency is detected, the problem has no solution with the current set of domains and constraints. * @implNote The propagation engine is ensured to be empty (no pending events) after this method. * Indeed, if no contradiction occurs, a fix point is reached. * Otherwise, a call to {@link PropagationEngine#flush()} is made. */ public void propagate() throws ContradictionException { if (!engine.isInitialized()) { engine.initialize(); } if (mModel.getHook(Model.TASK_SET_HOOK_NAME) != null) { //noinspection unchecked ArrayList tset = (ArrayList) mModel.getHook(Model.TASK_SET_HOOK_NAME); for (int i = 0; i < tset.size(); i++) { tset.get(i).ensureBoundConsistency(); } } try { engine.propagate(); } finally { engine.flush(); } } /** * Return the minimum conflicting set from a conflicting set that is causing contradiction. * * @param conflictingSet the super-set of constraints causing contradiction * @return minimumConflictingSet of constraints (the root cause of contradiction) * @throws SolverException when MCS is called during solving */ public List findMinimumConflictingSet(List conflictingSet) { if (isSolving()) { throw new SolverException("Minimum Conflicting Set (MCS) can't be executed during solving"); } return new QuickXPlain(getModel()).findMinimumConflictingSet(conflictingSet); } /** * Sets the following action in the search to be a restart instruction. * Note that the restart may not be immediate */ public void restart() { searchMonitors.beforeRestart(); restoreRootNode(); mModel.getEnvironment().worldPush(); getMeasures().incRestartCount(); try { objectivemanager.postDynamicCut(); mMeasures.incFixpointCount(); doPropagate(); action = extend; } catch (ContradictionException e) { // trivial inconsistency is detected, due to the cut stop = true; } searchMonitors.afterRestart(); } /** * Retrieves the state of the root node (after the initial propagation) * Has an immediate effect */ private void restoreRootNode() { IEnvironment environment = mModel.getEnvironment(); while (environment.getWorldIndex() > searchWorldIndex) { getMeasures().incBackTrackCount(); environment.worldPop(); } dpath.synchronize(); } /** *

* Move forward in the search space by adding a new decision. * A call to this method will : *

    *
  1. add dec to the decision path
  2. *
  3. push a back-up copy of internal states
  4. *
  5. propagate
  6. *
*

* Steps 1. and 2. are ignored when dec is null. *

* In case of success, a call moveForward(Decision) is possible. * Otherwise, a call {@link #moveBackward()} is required to keep on exploring the search space. * If no such call is done, the state maybe inconsistent with the decision path. *

*

* Example of usage: looking for all solutions of a problem. *

*
 {@code
     * // Declare model, variables and constraints, then
     * Decision dec = null;
     * boolean search = true;
     * while(search) {
     *     if (solver.moveForward(dec)) {
     *         dec = strategy.getDecision();
     *         if (dec == null) {
     *             // here a solution is found
     *         }else {
     *             continue;
     *         }
     *     }
     *     search = solver.moveBackward();
     *     dec = strategy.getDecision();
     * }
     * }
* * @param decision decision to add, can be null. * @return true if extension is successful, false otherwise. * @see #moveBackward() * @see #getDecisionPath() * @see AbstractStrategy#getDecision() */ public boolean moveForward(Decision decision) { if (!engine.isInitialized()) { engine.initialize(); } if (this.getEnvironment().getWorldIndex() == 0) { this.getEnvironment().worldPush(); } boolean success = true; if (decision != null) { // null means there is no more decision this.getDecisionPath().pushDecision(decision); this.getEnvironment().worldPush(); this.getDecisionPath().buildNext(); } try { this.getDecisionPath().apply(); this.getObjectiveManager().postDynamicCut(); this.getEngine().propagate(); } catch (ContradictionException cex) { engine.flush(); success = false; } return success; } /** *

* Move backward in the search space. * A call to this method will : *
    *
  1. pop the last copy of internal states
  2. *
  3. refute the last decision of the decision path
  4. *
  5. propagate
  6. *
* If step 2. is not possible or step 3. throws a failure, * the last decision of the decision path is popped and the three-step loop is applied * until a successful refutation or emptying decision path. *

* In case of success, a call {@link #moveForward(Decision)} is possible. *

*

* Example of usage: looking for all solutions of a problem. *

*
 {@code
     * // Declare model, variables and constraints, then
     * Decision dec = null;
     * boolean search = true;
     * while(search) {
     *     if (solver.moveForward(dec)) {
     *         dec = strategy.getDecision();
     *         if (dec == null) {
     *             // here a solution is found
     *         }else {
     *             continue;
     *         }
     *     }
     *     search = solver.moveBackward();
     *     dec = strategy.getDecision();
     * }
     * }
* * @return true in case of success, false otherwise * @see #moveForward(Decision) * @see #getDecisionPath() */ public boolean moveBackward() { this.getEnvironment().worldPop(); boolean success = false; Decision head = dpath.getLastDecision(); while (!success && head.getPosition() > 0) { if (head.hasNext()) { this.getEnvironment().worldPush(); this.getDecisionPath().buildNext(); try { this.getDecisionPath().apply(); this.getObjectiveManager().postDynamicCut(); this.getEngine().propagate(); success = true; } catch (ContradictionException cex) { engine.flush(); } } else { dpath.synchronize(); this.getEnvironment().worldPop(); } head = dpath.getLastDecision(); } return success; } /** * Solving is executing if the search state is different from NEW, that is, * if it has started to branch decisions. * A double check for execution is done looking if the environment trailing * has started as well. * * @return isSolving if the solver is executing searching or branching */ public boolean isSolving() { boolean isSearching = getSearchState() != SearchState.NEW; boolean isTrailing = getEnvironment().getWorldIndex() > rootWorldIndex; return isSearching || isTrailing; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// GETTERS ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * @return the model of this resolver */ public Model getModel() { return mModel; } /** * @return the current learn. */ public Learn getLearner() { return L; } /** * @return the current move. */ public Move getMove() { return M; } /** * @deprecated */ @Deprecated public Propagate getPropagate() { return null; } /** * @return the backtracking environment used for this solver */ public IEnvironment getEnvironment() { return getModel().getEnvironment(); } /** * @return the current decision path */ public DecisionPath getDecisionPath() { return dpath; } /** * @param kind of variables the search strategy deals with * @return the current search strategy in use */ public AbstractStrategy getSearch() { if (M.getChildMoves().size() > 1 && mModel.getSettings().warnUser()) { logger.bold().println( "This search loop is based on a sequential Move, the returned strategy may not reflect the reality."); } return M.getStrategy(); } /** * @param type of the objective variable * @return the currently used objective manager */ @SuppressWarnings("unchecked") public IObjectiveManager getObjectiveManager() { return objectivemanager; } /** * Indicates if the default search strategy is used * * @return false if a specific search strategy is used */ public boolean isDefaultSearchUsed() { return defaultSearch; } /** * Indicates if the search strategy is completed with one over all variables * * @return false if no strategy over all variables complete the declared one */ public boolean isSearchCompleted() { return completeSearch; } /** * @return true if the search loops ends unexpectedly (externally killed, for instance). */ @SuppressWarnings("unused") public boolean hasEndedUnexpectedly() { return mMeasures.getSearchState() == SearchState.KILLED; } /** * @return true if the search loops encountered at least one of the stop criteria declared. */ public boolean isStopCriterionMet() { boolean ismet = false; for (int i = 0; i < criteria.size() && !ismet; i++) { ismet = criteria.get(i).isMet(); } return ismet; } /** * @return the index of the world where the search starts, after initialization. */ public int getSearchWorldIndex() { return searchWorldIndex; } /** * Returns a reference to the measures recorder. * This enables to get, for instance, the number of solutions found, time count, etc. * * @return this model's measure recorder */ public MeasuresRecorder getMeasures() { //TODO Should the user have write-permission on the solver measures ? return mMeasures; } /** * Return the events observer plugged into {@code this}. * * @return this events observer */ public AbstractEventObserver getEventObserver() { return eventObserver; } /** * @return the propagation engine used in {@code this}. */ public PropagationEngine getEngine() { return engine; } /** * Returns information on the feasibility of the current problem defined by the solver. *

* Possible back values are: *
- {@link ESat#TRUE}: a solution has been found, *
- {@link ESat#FALSE}: the CSP has been proven to have no solution, *
- {@link ESat#UNDEFINED}: no solution has been found so far (within given limits) * without proving the unfeasibility, though. * * @return an {@link ESat}. */ public ESat isFeasible() { return feasible; } /** * Return the current state of the CSP. *

* Given the current domains, it can return a value among: *
- {@link ESat#TRUE}: all constraints of the CSP are satisfied for sure, *
- {@link ESat#FALSE}: at least one constraint of the CSP is not satisfied. *
- {@link ESat#UNDEFINED}: neither satisfiability nor unsatisfiability could be proven so far. *

* Presumably, not all variables are instantiated. * * @return ESat.TRUE if all constraints of the problem are satisfied, * ESat.FLASE if at least one constraint is not satisfied, * ESat.UNDEFINED neither satisfiability nor unsatisfiability could be proven so far. */ public ESat isSatisfied() { int OK = 0; for (Constraint c : mModel.getCstrs()) { if (c.isEnabled()) { ESat satC = c.isSatisfied(); if (FALSE == satC) { if (getModel().getSettings().warnUser()) { logger.bold().red().printf("FAILURE >> %s (%s)%n", c, satC); } return FALSE; } else if (TRUE == satC) { OK++; } } else { OK++; } } if (OK == mModel.getCstrs().length) { return TRUE; } else { return UNDEFINED; } } /** * @return how many worlds should be rolled back when backtracking (usually 1) */ public int getJumpTo() { return jumpTo; } /** * @return true when learning algorithm is not plugged in */ public boolean isLearnOff() { return L instanceof LearnNothing; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// SETTERS ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Replaces the current learn with {@code l} * * @param l the new learn to apply */ public void setLearner(Learn l) { this.L = l; } /** * Replaces the current move with {@code m} * * @param m the new move to apply */ public void setMove(Move... m) { if (m == null) { this.M = null; } else if (m.length == 1) { this.M = m[0]; } else { this.M = new MoveSeq(getModel(), m); } } /** * @deprecated */ @Deprecated public void setPropagate(Propagate p) { } /** * Add or complete a restart policy. * * @param restarter restarter policy * @implNote There can be multiple restart policies, stored in as linked list. * @see #clearRestarter() */ public void addRestarter(AbstractRestart restarter) { if (restarter != AbstractRestart.NO_RESTART) { restarter.setNext(this.restarter); this.restarter = restarter; } } /** * @return the current declared restart policy or {@link AbstractRestart#NO_RESTART} */ public AbstractRestart getRestarter() { return this.restarter; } /** * Clear the declared restart strategy. * Consequently, no restarting will occur. * * @implNote replace the declared restart policy by {@link AbstractRestart#NO_RESTART} */ public void clearRestarter() { this.restarter = AbstractRestart.NO_RESTART; } /** * Declares an objective manager to use. * * @param om the objective manager to use instead of the declared one (if any). */ public void setObjectiveManager(IObjectiveManager om) { this.objectivemanager = om; mMeasures.setBoundsManager(om); } /** * Override the default search strategies to use in {@code this}. * In case many strategies are given, they will be called in sequence: * The first strategy in parameter is first called to compute a decision, if possible. * If it cannot provide a new decision, the second strategy is called ... * and so on, until the last strategy. *

* * @param strategies the search strategies to use. */ @SuppressWarnings({"rawtypes", "ReassignedVariable", "unchecked"}) public void setSearch(AbstractStrategy... strategies) { if (strategies == null || strategies.length == 0) { throw new UnsupportedOperationException("no search strategy has been specified"); } if (M.getChildMoves().size() > 1) { throw new UnsupportedOperationException("The Move declared is composed of many Moves.\n" + "A strategy must be attached to each of them independently, and it cannot be achieved calling this method." + "An iteration over it child moves is needed: this.getMove().getChildMoves()."); } else { strategies = Arrays.stream(strategies).filter(Objects::nonNull) .flatMap(s -> (s instanceof StrategiesSequencer) ? Arrays.stream(((StrategiesSequencer) s).getStrategies()) : Stream.of(s)) .toArray(AbstractStrategy[]::new); if (strategies.length == 0) { M.removeStrategy(); } else if (strategies.length == 1) { M.setStrategy(strategies[0]); } else { M.setStrategy(Search.sequencer(strategies)); } } } /** * Overrides the explanation engine. * * @param explainer the explanation to use */ public void setEventObserver(AbstractEventObserver explainer) { this.eventObserver = explainer; } /** * Attaches a propagation engine {@code this}. * It overrides the previously defined one, only * if no propagation was done yet. * Indeed, some incremental propagators may have set up their internal structure, * which cannot be set up twice safely. *

* If propagation was done calling {@link #solve()}, * calling {@link #reset()} enables to set the propagation engine anew. *

* If propagation was done "manually" (calling {@link #propagate()}, then nothing can be done. * * @param propagationEngine a propagation strategy * @throws SolverException is already initialized. */ public void setEngine(PropagationEngine propagationEngine) { if (!engine.isInitialized() || getEnvironment().getWorldIndex() == rootWorldIndex) { this.engine = propagationEngine; } else { throw new SolverException("Illegal propagation engine modification."); } } /** * Completes (or not) the declared search strategy with one over all variables * * @param isComplete set to true to complete the current search strategy */ @SuppressWarnings("WeakerAccess") public void makeCompleteStrategy(boolean isComplete) { this.completeSearch = isComplete; } /** * Declare a warm start strategy that consists of a set of variables and a set of values. * It allows to define either a solution or at least a partial solution in order to drive the search toward * a solution. *

Such a (partial) solution serves only once. *

Note that a variable can appears more than once. */ public void addHint(IntVar var, int val) { if (warmStart == null) { warmStart = new WarmStart(this); } warmStart.addHint(var, val); } /** * Remove declare hints */ public void removeHints() { setSearch(warmStart.getStrategy()); warmStart.clearHints(); warmStart = null; } /** * Adds a stop criterion, which, when met, stops the search loop. * There can be multiple stop criteria, a logical OR is then applied. * The stop criteria are declared to the search loop just before launching the search, * the previously defined ones are erased. *

* There is no check if there are any duplicates. *

*
* Examples: *
* With a built-in counter, stop after 20 seconds: *

     *         SMF.limitTime(solver, "20s");
     * 
* With lambda, stop when 10 nodes are visited: *
     *     () -> solver.getNodeCount() >= 10
     * 
* * @param criterion one or many stop criterion to add. * @see #removeStopCriterion(Criterion...) * @see #removeAllStopCriteria() */ public void addStopCriterion(Criterion... criterion) { if (criterion != null) { Collections.addAll(criteria, criterion); } } /** * Removes one or many stop criterion from the one to declare to the search loop. * * @param criterion criterion to remove */ public void removeStopCriterion(Criterion... criterion) { if (criterion != null) { for (Criterion c : criterion) { criteria.remove(c); } } } /** * Empties the list of stop criteria declared. * This is automatically called on {@link #reset()}. */ @SuppressWarnings("WeakerAccess") public void removeAllStopCriteria() { this.criteria.clear(); } /** * @return the list of search monitors plugged in this resolver */ public SearchMonitorList getSearchMonitors() { return searchMonitors; } /** * Put a search monitor to react on search events (solutions, decisions, fails, ...). * Any search monitor is actually plugged just before the search starts. *

* There is no check if there are any duplicates. * A search monitor added during while the resolution has started will not be taken into account. * * @param sm a search monitor to be plugged in the solver */ public void plugMonitor(ISearchMonitor sm) { searchMonitors.add(sm); } /** * Removes a search monitors from the ones to plug when the search will start. * * @param sm a search monitor to be unplugged in the solver */ public void unplugMonitor(ISearchMonitor sm) { searchMonitors.remove(sm); } /** * Operation to execute when a solution is found * * @param r operation to execute */ public void onSolution(Runnable r) { searchMonitors.add((IMonitorSolution) r::run); } /** * Empties the list of search monitors. */ @SuppressWarnings("WeakerAccess") public void unplugAllSearchMonitors() { searchMonitors.reset(); } /** * Sets how many worlds to rollback when backtracking * * @param jto how many worlds to rollback when backtracking */ public void setJumpTo(int jto) { this.jumpTo = jto; } /** * The first call to this method will create a new solution based on all variables * of the model and attach it to this. * Next calls return the solution instance. * * @return a global solution. */ public Solution defaultSolution() { if (lastSol == null) { lastSol = new Solution(this.getModel()); this.attach(lastSol); } return lastSol; } /** * @return whether or not the default solution object has been created */ public boolean defaultSolutionExists() { return lastSol != null; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// FACTORY ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public Solver ref() { return this; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// MEASURES ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public String getModelName() { return getMeasures().getModelName(); } @Override public long getTimestamp() { return getMeasures().getTimestamp(); } @Override public float getTimeCount() { return getMeasures().getTimeCount(); } @Override public long getTimeCountInNanoSeconds() { return getMeasures().getTimeCountInNanoSeconds(); } @Override public long getTimeToBestSolutionInNanoSeconds() { return getMeasures().getTimeToBestSolutionInNanoSeconds(); } @Override public long getReadingTimeCountInNanoSeconds() { return getMeasures().getReadingTimeCountInNanoSeconds(); } @Override public float getReadingTimeCount() { return getMeasures().getReadingTimeCount(); } @Override public long getNodeCount() { return getMeasures().getNodeCount(); } @Override public long getBackTrackCount() { return getMeasures().getBackTrackCount(); } @Override public long getBackjumpCount() { return getMeasures().getBackjumpCount(); } @Override public long getFailCount() { return getMeasures().getFailCount(); } @Override public long getFixpointCount() { return getMeasures().getFixpointCount(); } @Override public long getRestartCount() { return getMeasures().getRestartCount(); } @Override public long getSolutionCount() { return getMeasures().getSolutionCount(); } @Override public long getDecisionCount() { return getMeasures().getDecisionCount(); } @Override public long getMaxDepth() { return getMeasures().getMaxDepth(); } @Override public long getCurrentDepth() { return getDecisionPath().size(); } @Override public boolean hasObjective() { return getMeasures().hasObjective(); } @Override public boolean isObjectiveOptimal() { return getMeasures().isObjectiveOptimal(); } @Override public Number getBestSolutionValue() { return getMeasures().getBestSolutionValue(); } @Override public SearchState getSearchState() { return getMeasures().getSearchState(); } /** * @return the currently used objective manager */ @Override public IBoundsManager getBoundsManager() { assert getMeasures().getBoundsManager() == objectivemanager; return getMeasures().getBoundsManager(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// OUTPUT //////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Return the current used logger. * By default, logger prints to {@link System#out}. * Any trace from choco-solver are redirected to this logger. * * @return the current logger. * @see #logWithANSI(boolean) */ public Logger log() { return logger; } /** * Defines whether (when {@code ansi} is set to {@code true}) or not * ANSI tags are added to any trace from choco-solver. * * @param ansi {@code true} to enable colors */ public void logWithANSI(boolean ansi) { logger = ansi ? new ANSILogger(logger) : new Logger(logger); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy