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

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

There is a newer version: 4.10.17
Show newest version
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2022, 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.cutoffseq.LubyCutoffStrategy;
import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.constraints.nary.sat.NogoodStealer;
import org.chocosolver.solver.constraints.real.RealConstraint;
import org.chocosolver.solver.exception.InvalidSolutionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.search.limits.FailCounter;
import org.chocosolver.solver.search.loop.lns.INeighborFactory;
import org.chocosolver.solver.search.loop.monitors.IMonitorSolution;
import org.chocosolver.solver.search.loop.monitors.NogoodFromRestarts;
import org.chocosolver.solver.search.strategy.Search;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.RealVar;
import org.chocosolver.solver.variables.SetVar;
import org.chocosolver.solver.variables.Variable;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static org.chocosolver.solver.search.strategy.Search.*;

/**
 * 

* A Portfolio helper. *

*

* The ParallelPortfolio resolution of a problem is made of four steps: *

    *
  1. adding models to be run in parallel,
  2. *
  3. running resolution in parallel,
  4. *
  5. getting the model which finds a solution (or the best one), if any.
  6. *
* Each of the four steps is needed and the order is imposed too. * In particular, in step 1. each model should be populated individually with a model of the problem * (presumably the same model, but not required). * Populating model is not managed by this class and should be done before applying step 2., * with a dedicated method for instance. *
* Note also that there should not be pending resolution process in any models. * Otherwise, unexpected behaviors may occur. *

*

* The resolution process is synchronized. As soon as one model ends (naturally or by hitting a limit) * the other ones are eagerly stopped. * Moreover, when dealing with an optimization problem, cut on the objective variable's value is propagated * to all models on solution. * It is essential to eagerly declare the objective variable(s) with {@link Model#setObjective(boolean, Variable)}. * *

*

* Note that the similarity of the models declared is not required. * However, when dealing with an optimization problem, keep in mind that the cut on the objective variable's value * is propagated among all models, so different objectives may lead to wrong results. *

*

* Since there is no condition on the similarity of the models, * once the resolution ends, the model which finds the (best) solution is internally stored. *

*

* Example of use. * *

 * ParallelPortfolio pares = new ParallelPortfolio();
 * int n = 4; // number of models to use
 * for (int i = 0; i < n; i++) {
 *      pares.addModel(modeller());
 * }
 * pares.solve();
 * IOutputFactory.printSolutions(pares.getBestModel());
 * 
 * 
* *

*

* This class uses Java 8 streaming feature, and may be not compliant with older versions. *

* * *

* Project: choco. * * @author Charles Prud'homme, Jean-Guillaume Fages * @since 23/12/2015. */ public class ParallelPortfolio { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// VARIABLES ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * List of {@link Model}s to be executed in parallel. */ private final List models; /** * whether or not to use default search configurations for the different threads **/ private final boolean searchAutoConf; /** * This manager is used to synchronize nogood sharing. */ private NogoodStealer manager = NogoodStealer.NONE; /** * Stores whether or not prepare() method has been called */ private boolean isPrepared = false; /** * List of {@link Model}s to be executed in parallel. */ private final HashMap reliableness; private final AtomicBoolean solverTerminated = new AtomicBoolean(false); private final AtomicBoolean solutionFound = new AtomicBoolean(false); private final AtomicInteger solverRunning = new AtomicInteger(0); /** * Point to (one of) the solver(s) which found a solution */ private Model finder; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// CONSTRUCTOR ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Creates a new ParallelPortfolio * This class stores the models to be executed in parallel in a {@link ArrayList} initially empty. * * @param searchAutoConf changes the search heuristics of the different solvers, except the first one (true by default). * Must be set to false if search heuristics of the different threads are specified manually, so that they are not erased */ public ParallelPortfolio(boolean searchAutoConf) { this.models = new ArrayList<>(); this.reliableness = new HashMap<>(); this.searchAutoConf = searchAutoConf; } /** * Creates a new ParallelPortfolio * This class stores the models to be executed in parallel in a {@link ArrayList} initially empty. * Search heuristics will be changed automatically (except for the first thread that will remain in the same configuration). */ public ParallelPortfolio() { this(true); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// API ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Calling this method will ensure that workers equipped with a restart policy not only * record nogoods from themselves (based on {@link NogoodFromRestarts}) but also based on * other workers of the portfolio. * * @implSpec It is assumed that all models in this portfolio are equivalent (ie, each variable has * the same ID in each worker). */ public void stealNogoodsOnRestarts() { this.manager = new NogoodStealer(); } /** *

* Adds a model to the list of models to run in parallel. * The model can either be a fresh one, ready for populating, or a populated one. *

*

* Important: *

    *
  • the populating process is not managed by this ParallelPortfolio * and should be done externally, with a dedicated method for example. *
  • *
  • * when dealing with optimization problems, the objective variables HAVE to be declared eagerly with * {@link Model#setObjective(boolean, Variable)}. *
  • *
* *

* * @param model a model to add */ public void addModel(Model model) { addModel(model, true); } /** *

* Adds a model to the list of models to run in parallel. * The model can either be a fresh one, ready for populating, or a populated one. *

*

* Important: *

    *
  • the populating process is not managed by this {@code ParallelPortfolio} * and should be done externally, with a dedicated method for example. *
  • *
  • * when dealing with optimization problems, the objective variables HAVE to be declared eagerly with * {@link Model#setObjective(boolean, Variable)}. *
  • *
* *

*

*

* A reliable model is expected to prove the absence of a solution, * improving one in the case of optimisation problem. * A model with non-redundant constraints posted * to improve resolution at the expense of completeness is considered unreliable. * An unreliable model cannot share its no-goods and when it stops, cannot stop other models. *

*

* There should be at least one reliable model in a Portfolio. * Otherwise, solving may be made incomplete. *

* * @param model a model to add * @param reliable set to {@code true} if the model is reliable. */ public void addModel(Model model, boolean reliable) { this.models.add(model); this.reliableness.put(model, reliable); } /** * Run the solve() instruction of every model of the portfolio in parallel. * *

* Note that a call to {@link #getBestModel()} returns a model which has found the best solution. *

* * @return true if and only if at least one new solution has been found. * @throws SolverException if no model or only model has been added. */ public boolean solve() { getSolverTerminated().set(false); getSolutionFound().set(false); getSolverRunning().set(models.size()); if (!isPrepared) { prepare(); } ForkJoinPool forkJoinPool = new ForkJoinPool(models.size()); try { forkJoinPool.submit(() -> models.parallelStream().forEach(m -> { if (!getSolverTerminated().get()) { boolean so = m.getSolver().solve(); if (!so || finder == m) { getSolverTerminated().set(so || reliableness.get(m) || getSolverRunning().decrementAndGet() <= 0); } } })).get(); } catch (InterruptedException | ExecutionException | SolverException e) { getSolverRunning().decrementAndGet(); //If a InvalidSolutionException occurs and at least one model is not reliable // the exception may come from this model and should be ignored if (e.getCause() instanceof InvalidSolutionException) { InvalidSolutionException ex = (InvalidSolutionException) e.getCause(); if (reliableness.get(ex.getModel())) { throw (SolverException) e.getCause(); }// else ignore the error } else { e.printStackTrace(); } } forkJoinPool.shutdownNow(); getSolverTerminated().set(false);// otherwise, solver.isStopCriterionMet() always returns true if (getSolutionFound().get() && models.get(0).getResolutionPolicy() != ResolutionPolicy.SATISFACTION) { int bestAll = getBestModel().getSolver().getBestSolutionValue().intValue(); for (Model m : models) { int mVal = m.getSolver().getBestSolutionValue().intValue(); if (m.getResolutionPolicy() == ResolutionPolicy.MAXIMIZE) { assert mVal <= bestAll : mVal + " > " + bestAll; } else assert m.getResolutionPolicy() != ResolutionPolicy.MINIMIZE || mVal >= bestAll : mVal + " < " + bestAll; } } return getSolutionFound().get(); } /** * Returns the first model from the list which, either : *
    *
  • * finds a solution when dealing with a satisfaction problem, *
  • *
  • * or finds (and possibly proves) the best solution when dealing with an optimization problem. *
  • *
* or null if no such model exists. * Note that there can be more than one "finder" in the list, yet, this method returns the index of the first one. * * @return the first model which finds a solution (or the best one) or null if no such model exists. */ public Model getBestModel() { return finder; } /** * @return the (mutable!) list of models used in this ParallelPortfolio */ public List getModels() { return models; } /** * Attempts to find all solutions of the declared problem. *
    *
  • If the method returns an empty list:
  • *
      *
    • either a stop criterion (e.g., a time limit) stops the search before any solution has been found,
    • *
    • or no solution exists for the problem (i.e., over-constrained).
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all solutions have been found.
    • *
    *
*

*

* Note that all variables will be recorded * * @return a list that contained the found solutions. */ public Stream streamSolutions() { Spliterator it = new Spliterator() { @Override public boolean tryAdvance(Consumer action) { if (solve()) { action.accept(new Solution(getBestModel()).record()); return true; } return false; } @Override public Spliterator trySplit() { return null; } @Override public long estimateSize() { return Long.MAX_VALUE; } @Override public int characteristics() { return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.CONCURRENT; } }; return StreamSupport.stream(it, false); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// INTERNAL METHODS ////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") public void prepare() { isPrepared = true; check(); for (int i = 0; i < models.size(); i++) { Solver s = models.get(i).getSolver(); s.addStopCriterion(() -> getSolverTerminated().get()); s.plugMonitor((IMonitorSolution) () -> updateFromSolution(s.getModel())); if (searchAutoConf) { configureModel(i); } } } private synchronized void updateFromSolution(Model m) { if (m.getResolutionPolicy() == ResolutionPolicy.SATISFACTION) { finder = m; getSolutionFound().set(true); } else { int solverVal = ((IntVar) m.getObjective()).getValue(); int bestVal = m.getSolver().getObjectiveManager().getBestSolutionValue().intValue(); if (m.getResolutionPolicy() == ResolutionPolicy.MAXIMIZE) { assert solverVal <= bestVal : solverVal + ">" + bestVal; } else assert m.getResolutionPolicy() != ResolutionPolicy.MINIMIZE || solverVal >= bestVal : solverVal + "<" + bestVal; if (solverVal == bestVal) { getSolutionFound().set(true); finder = m; if (m.getResolutionPolicy() == ResolutionPolicy.MAXIMIZE) { models.forEach(s1 -> s1.getSolver().getObjectiveManager().updateBestLB(bestVal)); } else { models.forEach(s1 -> s1.getSolver().getObjectiveManager().updateBestUB(bestVal)); } } } } private void configureModel(int workerID) { Model worker = getModels().get(workerID); Solver solver = worker.getSolver(); ResolutionPolicy policy = worker.getResolutionPolicy(); // compute decision variables Variable[] varsX; if (solver.getSearch() != null && solver.getSearch().getVariables().length > 0) { varsX = solver.getSearch().getVariables(); } else { varsX = worker.getVars(); } IntVar[] ivars = new IntVar[varsX.length]; SetVar[] svars = new SetVar[varsX.length]; RealVar[] rvars = new RealVar[varsX.length]; int ki = 0, ks = 0, kr = 0; for (Variable aVarsX : varsX) { if ((aVarsX.getTypeAndKind() & Variable.INT) > 0) { ivars[ki++] = (IntVar) aVarsX; } else if ((aVarsX.getTypeAndKind() & Variable.SET) > 0) { svars[ks++] = (SetVar) aVarsX; } else if ((aVarsX.getTypeAndKind() & Variable.REAL) > 0) { rvars[kr++] = (RealVar) aVarsX; } else { throw new UnsupportedOperationException("unrecognized variable kind " + aVarsX); } } ivars = Arrays.copyOf(ivars, ki); svars = Arrays.copyOf(svars, ks); rvars = Arrays.copyOf(rvars, kr); // set heuristic boolean opt = policy != ResolutionPolicy.SATISFACTION; switch (workerID) { case 0: // DWD + fast restart + LC (+ B2V) solver.setSearch( lastConflict( VarH.DOMWDEG.make(solver, ivars, ValH.BEST, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); if (reliableness.containsKey(worker)) { manager.add(worker); } break; case 1: solver.setSearch( lastConflict( VarH.CHS.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); if (reliableness.containsKey(worker)) { manager.add(worker); } break; case 2: // input order + LC solver.setSearch( lastConflict( VarH.INPUT.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); if (reliableness.containsKey(worker)) { manager.add(worker); } break; case 3: if (!opt) { solver.setSearch( lastConflict( VarH.DOMWDEGR.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); break; } else { // input order + LC + LNS solver.setSearch( lastConflict( VarH.INPUT.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); solver.setLNS(INeighborFactory.blackBox(ivars), new FailCounter(solver.getModel(), 1000)); } if (reliableness.containsKey(worker)) { manager.add(worker); } break; case 4: // ABS + fast restart + LC solver.setSearch( lastConflict( VarH.ABS.make(solver, ivars, ValH.DEFAULT, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); break; case 5: // DWD + fast restart + COS solver.setSearch( Search.conflictOrderingSearch( VarH.DOMWDEG.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); solver.setSearch(lastConflict(solver.getSearch())); break; case 6: // DWD + fast restart + LC (+ B2V) if (!opt) { solver.setSearch(VarH.DOMWDEG.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, false)); } else { solver.setSearch( lastConflict( VarH.DOMWDEGR.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 500, 0.d, 5000); } break; case 7: solver.setSearch( lastConflict( VarH.CHS.make(solver, ivars, ValH.MIN, Integer.MAX_VALUE, opt) ) ); Restarts.LUBY.declare(solver, 40, 0.d, 5000); if (reliableness.containsKey(worker)) { manager.add(worker); } break; default: // random search (various seeds) + LNS if optim solver.setSearch(lastConflict(randomSearch(ivars, workerID))); if (policy != ResolutionPolicy.SATISFACTION) { solver.setLNS(INeighborFactory.blackBox(ivars), new FailCounter(solver.getModel(), 1000)); } if (reliableness.containsKey(worker)) { solver.plugMonitor(new NogoodFromRestarts(worker, manager)); } solver.setRestarts(count -> solver.getFailCount() >= count, new LubyCutoffStrategy(500), 5000); break; } // complete with set default search if (ks > 0) { solver.setSearch(solver.getSearch(), setVarSearch(svars)); } // complete with real default search if (kr > 0) { solver.setSearch(solver.getSearch(), realVarSearch(rvars)); } } private void check() { if (models.size() == 0) { throw new SolverException("No model found in the ParallelPortfolio."); } if (models.get(0).getResolutionPolicy() != ResolutionPolicy.SATISFACTION) { Variable objective = models.get(0).getObjective(); if (objective == null) { throw new UnsupportedOperationException("No objective has been defined"); } if ((objective.getTypeAndKind() & Variable.REAL) != 0) { for (Constraint c : models.get(0).getCstrs()) { if (c instanceof RealConstraint) { throw new UnsupportedOperationException("" + "Ibex is not multithread safe, ParallelPortfolio cannot be used"); } } } } } private synchronized AtomicBoolean getSolverTerminated() { return solverTerminated; } private synchronized AtomicBoolean getSolutionFound() { return solutionFound; } private synchronized AtomicInteger getSolverRunning() { return solverRunning; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy