org.chocosolver.solver.Model Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of choco-solver Show documentation
Show all versions of choco-solver Show documentation
Open-source constraint solver.
/**
* Copyright (c) 2016, Ecole des Mines de Nantes
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the .
* 4. Neither the name of the nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.chocosolver.solver;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.chocosolver.memory.EnvironmentBuilder;
import org.chocosolver.memory.IEnvironment;
import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.constraints.nary.cnf.PropFalse;
import org.chocosolver.solver.constraints.nary.cnf.PropTrue;
import org.chocosolver.solver.constraints.nary.cnf.SatConstraint;
import org.chocosolver.solver.constraints.nary.nogood.NogoodConstraint;
import org.chocosolver.solver.constraints.real.Ibex;
import org.chocosolver.solver.constraints.reification.ConDisConstraint;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.objective.IObjectiveManager;
import org.chocosolver.solver.objective.ObjectiveFactory;
import org.chocosolver.solver.propagation.IPropagationEngine;
import org.chocosolver.solver.propagation.NoPropagationEngine;
import org.chocosolver.solver.propagation.PropagationTrigger;
import org.chocosolver.solver.variables.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* The Model
is the header component of Constraint Programming.
* It embeds the list of Variable
(and their Domain
), the Constraint
's network,
* and a IPropagationEngine
to pilot the propagation.
* Model
includes a AbstractSearchLoop
to guide the search loop: applying decisions and propagating,
* running backups and rollbacks and storing solutions.
*
* @author Xavier Lorca
* @author Charles Prud'homme
* @author Jean-Guillaume Fages
* @author Arnaud Malapert
* @version 0.01, june 2010
* @see org.chocosolver.solver.variables.Variable
* @see org.chocosolver.solver.constraints.Constraint
* @since 0.01
*/
public class Model implements IModel {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// PRIVATE FIELDS /////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static boolean MAXIMIZE = true;
public static boolean MINIMIZE = false;
/**
* Settings to use with this solver
*/
private Settings settings = new Settings() {
};
/**
* A map to cache constants (considered as fixed variables)
*/
private TIntObjectHashMap cachedConstants;
/**
* Variables of the model
*/
private Variable[] vars;
/**
* Index of the last added variable
*/
private int vIdx;
/**
* Constraints of the model
*/
private Constraint[] cstrs;
/**
* Index of the last added constraint
*/
private int cIdx;
/**
* Environment, based of the search tree (trailing or copying)
*/
private final IEnvironment environment;
/**
* Resolver of the model, controls propagation and search
*/
private final Solver solver;
/**
* Variable to optimize, possibly null.
*/
private Variable objective;
/**
* Precision to consider when optimizing a RealVariable
*/
private double precision = 0.0001D;
/**
* Model name
*/
private String name;
/**
* Stores this model's creation time
*/
private long creationTime;
/**
* Counter used to set ids to variables and propagators
*/
private int id = 1;
/**
* Counter used to name variables created internally
*/
private int nameId = 1;
/**
* A MiniSat instance, useful to deal with clauses
*/
protected SatConstraint minisat;
/**
* A MiniSat instance adapted to nogood management
*/
protected NogoodConstraint nogoods;
/**
* A CondisConstraint instance adapted to constructive disjunction management
*/
protected ConDisConstraint condis;
/**
* An Ibex (continuous constraint model) instance
*/
private Ibex ibex;
/**
* Enable attaching hooks to a model.
*/
private Map hooks;
/**
* Resolution policy (sat/min/max)
*/
private ResolutionPolicy policy = ResolutionPolicy.SATISFACTION;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// CONSTRUCTORS ///////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Creates a Model object to formulate a decision problem by declaring variables and posting constraints.
* The model is named name
and it uses a specific backtracking environment
.
*
* @param environment a backtracking environment to allow search
* @param name The name of the model (for logging purpose)
*/
public Model(IEnvironment environment, String name) {
this.name = name;
this.vars = new Variable[32];
this.vIdx = 0;
this.cstrs = new Constraint[32];
this.cIdx = 0;
this.environment = environment;
this.creationTime = System.currentTimeMillis();
this.cachedConstants = new TIntObjectHashMap<>(16, 1.5f, Integer.MAX_VALUE);
this.objective = null;
this.hooks = new HashMap<>();
this.solver = new Solver(this);
}
/**
* Creates a Model object to formulate a decision problem by declaring variables and posting constraints.
* The model is named name
and uses the default (trailing) backtracking environment.
*
* @param name The name of the model (for logging purpose)
* @see Model#Model(org.chocosolver.memory.IEnvironment, String)
*/
public Model(String name) {
this(new EnvironmentBuilder().fromFlat().build(), name);
}
/**
* Creates a Model object to formulate a decision problem by declaring variables and posting constraints.
* The model uses the default (trailing) backtracking environment.
*
* @see Model#Model(org.chocosolver.memory.IEnvironment, String)
*/
public Model() {
this("Model-" + nextModelNum());
}
/**
* For autonumbering anonymous models.
*/
private static int modelInitNumber;
/**
* @return next model's number, for anonymous models.
*/
private static synchronized int nextModelNum() {
return modelInitNumber++;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// GETTERS ////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the creation time (in milliseconds) of the model (to estimate modeling duration)
*
* @return the time (in ms) of the creation of the model
*/
public long getCreationTime() {
return creationTime;
}
/**
* Get the resolution policy of the model
*
* @return the resolution policy of the model
* @see ResolutionPolicy
*/
public ResolutionPolicy getResolutionPolicy() {
return policy;
}
/**
* Get the map of constant IntVar the have default names to avoid creating multiple identical constants.
* Should not be called by the user.
*
* @return the map of constant IntVar having default names.
*/
public TIntObjectHashMap getCachedConstants() {
return cachedConstants;
}
/**
* The basic "true" constraint, which is always satisfied
*
* @return a "true" constraint
*/
public Constraint trueConstraint() {
return new Constraint("TRUE cstr", new PropTrue(boolVar(true)));
}
/**
* The basic "false" constraint, which is always violated
*
* @return a "false" constraint
*/
public Constraint falseConstraint() {
return new Constraint("FALSE cstr", new PropFalse(boolVar(false)));
}
/**
* Returns the unique and internal propagation and search object to solve this model.
*
* @return the unique and internal Resolver
object.
*/
public Solver getSolver() {
return solver;
}
/**
* Returns the array of Variable
objects declared in this Model
.
*
* @return array of all variables in this model
*/
public Variable[] getVars() {
return Arrays.copyOf(vars, vIdx);
}
/**
* Returns the number of variables involved in this
.
*
* @return number of variables in this model
*/
public int getNbVars() {
return vIdx;
}
/**
* Returns the ith variable within the array of variables defined in this
.
*
* @param i index of the variable to return.
* @return the ith variable of this model
*/
public Variable getVar(int i) {
return vars[i];
}
/**
* Iterate over the variable of this
and build an array that contains all the IntVar of the model.
* excludes BoolVar if includeBoolVar=false.
* It also contains FIXED variables and VIEWS, if any.
*
* @param includeBoolVar indicates whether or not to include BoolVar
* @return array of IntVars in this
model
*/
public IntVar[] retrieveIntVars(boolean includeBoolVar) {
IntVar[] ivars = new IntVar[vIdx];
int k = 0;
for (int i = 0; i < vIdx; i++) {
int kind = (vars[i].getTypeAndKind() & Variable.KIND);
if (kind == Variable.INT || (includeBoolVar && kind == Variable.BOOL)) {
ivars[k++] = (IntVar) vars[i];
}
}
return Arrays.copyOf(ivars, k);
}
/**
* Iterate over the variable of this
and build an array that contains the BoolVar only.
* It also contains FIXED variables and VIEWS, if any.
*
* @return array of BoolVars in this
model
*/
public BoolVar[] retrieveBoolVars() {
BoolVar[] bvars = new BoolVar[vIdx];
int k = 0;
for (int i = 0; i < vIdx; i++) {
if ((vars[i].getTypeAndKind() & Variable.KIND) == Variable.BOOL) {
bvars[k++] = (BoolVar) vars[i];
}
}
return Arrays.copyOf(bvars, k);
}
/**
* Iterate over the variable of this
and build an array that contains the SetVar only.
* It also contains FIXED variables and VIEWS, if any.
*
* @return array of SetVars in this
model
*/
public SetVar[] retrieveSetVars() {
SetVar[] bvars = new SetVar[vIdx];
int k = 0;
for (int i = 0; i < vIdx; i++) {
if ((vars[i].getTypeAndKind() & Variable.KIND) == Variable.SET) {
bvars[k++] = (SetVar) vars[i];
}
}
return Arrays.copyOf(bvars, k);
}
/**
* Iterate over the variable of this
and build an array that contains the RealVar only.
* It also contains FIXED variables and VIEWS, if any.
*
* @return array of RealVars in this
model
*/
public RealVar[] retrieveRealVars() {
RealVar[] bvars = new RealVar[vIdx];
int k = 0;
for (int i = 0; i < vIdx; i++) {
if ((vars[i].getTypeAndKind() & Variable.KIND) == Variable.REAL) {
bvars[k++] = (RealVar) vars[i];
}
}
return Arrays.copyOf(bvars, k);
}
/**
* Returns the array of Constraint
objects posted in this Model
.
*
* @return array of posted constraints
*/
public Constraint[] getCstrs() {
return Arrays.copyOf(cstrs, cIdx);
}
/**
* Return the number of constraints posted in this
.
*
* @return number of posted constraints.
*/
public int getNbCstrs() {
return cIdx;
}
/**
* Return the name of this
model.
*
* @return this model's name
*/
public String getName() {
return name;
}
/**
* Return the backtracking environment of this
model.
*
* @return the backtracking environment of this model
*/
public IEnvironment getEnvironment() {
return environment;
}
/**
* Return the (possibly null) objective variable
*
* @return a variable (null for satisfaction problems)
*/
public Variable getObjective() {
return objective;
}
/**
* In case of real variable(s) to optimize, a precision is required.
*
* @return the precision used
*/
public double getPrecision() {
return precision;
}
/**
* Returns the object associated with the named hookName
*
* @param hookName the name of the hook to return
* @return the object associated to the name hookName
*/
public Object getHook(String hookName) {
return hooks.get(hookName);
}
/**
* Returns the map containing declared hooks.
* This map is mutable.
*
* @return the map of hooks.
*/
protected Map getHooks() {
return hooks;
}
/**
* Returns the unique constraint embedding a minisat model.
* A call to this method will create and post the constraint if it does not exist already.
*
* @return the minisat constraint
*/
public SatConstraint getMinisat() {
if (minisat == null) {
minisat = new SatConstraint(this);
minisat.post();
}
return minisat;
}
/**
* Return a constraint embedding a nogood store (based on a sat model).
* A call to this method will create and post the constraint if it does not exist already.
*
* @return the no good constraint
*/
public NogoodConstraint getNogoodStore() {
if (nogoods == null) {
nogoods = new NogoodConstraint(this);
nogoods.post();
}
return nogoods;
}
/**
* Return a constraint embedding a constructive disjunction store.
* A call to this method will create and post the constraint if it does not exist already.
*
* @return the constructive disjunction constraint
*/
public ConDisConstraint getConDisStore() {
if (condis == null) {
condis = new ConDisConstraint(this);
condis.post();
}
return condis;
}
/**
* Return the current settings for the solver
*
* @return a {@link org.chocosolver.solver.Settings}
*/
public Settings getSettings() {
return this.settings;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// SETTERS ////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Defines the variable to optimize (maximize or minimize)
* By default, each solution forces either :
*
* - for {@link Model#MAXIMIZE}: to increase by one {@link IntVar} (or {@link #precision} for {@link RealVar}) the objective lower bound, or
* - for {@link Model#MINIMIZE}: to decrease by one {@link IntVar} (or {@link #precision} for {@link RealVar}) the objective upper bound.
*
*
* @param maximize whether to maximize (true) or minimize (false) the objective
* @param objective variable to optimize
* @see IObjectiveManager#setStrictDynamicCut()
* @see IObjectiveManager#setWalkingDynamicCut()
* @see IObjectiveManager#setCutComputer(Function)
*/
@SuppressWarnings("unchecked")
public void setObjective(boolean maximize, Variable objective) {
if (objective == null) {
throw new SolverException("Cannot set objective to null");
} else {
this.policy = maximize ? ResolutionPolicy.MAXIMIZE : ResolutionPolicy.MINIMIZE;
this.objective = objective;
if ((objective.getTypeAndKind() & Variable.KIND) == Variable.REAL) {
getSolver().setObjectiveManager(
ObjectiveFactory.makeObjectiveManager((RealVar) objective, policy, precision)
);
} else {
getSolver().setObjectiveManager(
ObjectiveFactory.makeObjectiveManager((IntVar) objective, policy)
);
}
}
}
/**
* Removes any objective and set problem to a satisfaction problem
*/
public void clearObjective() {
this.objective = null;
this.policy = ResolutionPolicy.SATISFACTION;
getSolver().setObjectiveManager(ObjectiveFactory.SAT());
}
/**
* In case of real variable to optimize, a precision is required.
*
* @param p the precision (default is 0.0001D)
*/
public void setPrecision(double p) {
this.precision = p;
}
/**
* Override the default {@link org.chocosolver.solver.Settings} object.
*
* @param defaults new settings
*/
public void set(Settings defaults) {
this.settings = defaults;
}
/**
* Adds the hookObject
to store in this model, associated with the name hookName
.
* A hook is a simple map "hookName" <-> hookObject.
*
* @param hookName name of the hook
* @param hookObject hook to store
*/
public void addHook(String hookName, Object hookObject) {
this.hooks.put(hookName, hookObject);
}
/**
* Removes the hook named hookName
*
* @param hookName name of the hookObject to remove
*/
public void removeHook(String hookName) {
this.hooks.remove(hookName);
}
/**
* Empties the hooks attached to this model.
*/
public void removeAllHooks() {
this.hooks.clear();
}
/**
* Changes the name of this model to be equal to the argument name
.
*
* @param name the new name of this model.
*/
public void setName(String name) {
this.name = name;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// RELATED TO VAR ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Link a variable to this
. This is executed AUTOMATICALLY in variable constructor,
* so no checked are done on multiple occurrences of the very same variable.
* Should not be called by the user.
*
* @param variable a newly created variable, not already added
*/
public void associates(Variable variable) {
if (vIdx == vars.length) {
Variable[] tmp = vars;
vars = new Variable[tmp.length * 2];
System.arraycopy(tmp, 0, vars, 0, vIdx);
}
vars[vIdx++] = variable;
}
/**
* Unlink the variable from this
.
* Should not be called by the user.
*
* @param variable variable to un-associate
*/
public void unassociates(Variable variable) {
if (variable.getNbProps() > 0) {
throw new SolverException("Try to remove a variable (" + variable.getName() + ")which is still involved in at least one constraint");
}
int idx = 0;
for (; idx < vIdx; idx++) {
if (variable == vars[idx]) break;
}
System.arraycopy(vars, idx + 1, vars, idx + 1 - 1, vIdx - (idx + 1));
vars[--vIdx] = null;
}
/**
* Get a free single-use id to identify a new variable.
* Should not be called by the user.
*
* @return a free id to use
*/
public int nextId() {
return id++;
}
/**
* Get a free single-use name id to identify a variable created internally.
* Should not be called by the user.
*
* @return a free id to use
*/
public int nextNameId() {
return nameId++;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// RELATED TO CSTR DECLARATION ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Posts constraints cs
permanently in the constraints network of this
:
* - add them to the data structure,
* - set the fixed idx,
* - checks for restrictions
*
* @param cs Constraints
* @throws SolverException if the constraint is posted twice, posted although reified or reified twice.
*/
public void post(Constraint... cs) throws SolverException {
_post(true, cs);
}
/**
* Add constraints to the model.
*
* @param permanent specify whether the constraints are added permanently (if set to true) or temporary (ie, should be removed on backtrack)
* @param cs list of constraints
* @throws SolverException if a constraint is posted twice, posted although reified or reified twice.
*/
private void _post(boolean permanent, Constraint... cs) throws SolverException {
boolean dynAdd = false;
// check if the resolution already started -> if true, dynamic addition
IPropagationEngine engine = getSolver().getEngine();
if (engine != NoPropagationEngine.SINGLETON && engine.isInitialized()) {
dynAdd = true;
}
// then prepare storage of the constraints
if (cIdx + cs.length >= cstrs.length) {
int nsize = cstrs.length;
while (cIdx + cs.length >= nsize) {
nsize *= 3 / 2 + 1;
}
Constraint[] tmp = cstrs;
cstrs = new Constraint[nsize];
System.arraycopy(tmp, 0, cstrs, 0, cIdx);
}
// specific behavior for dynamic addition and/or reified constraints
for (Constraint c : cs) {
for (Propagator p : c.getPropagators()) {
p.getConstraint().checkNewStatus(Constraint.Status.POSTED);
p.linkVariables();
}
if (dynAdd) {
engine.dynamicAddition(permanent, c.getPropagators());
}
c.declareAs(Constraint.Status.POSTED, cIdx);
cstrs[cIdx++] = c;
}
}
/**
* Posts constraints cs
temporary, that is, they will be unposted upon backtrack.
*
* @param cs a set of constraints to add
* @throws ContradictionException if the addition of constraints cs
detects inconsistency.
* @throws SolverException if a constraint is posted twice, posted although reified or reified twice.
*/
public void postTemp(Constraint... cs) throws ContradictionException {
for (Constraint c : cs) {
_post(false, c);
if (getSolver().getEngine() == NoPropagationEngine.SINGLETON || !getSolver().getEngine().isInitialized()) {
throw new SolverException("Try to post a temporary constraint while the resolution has not begun.\n" +
"A call to Model.post(Constraint) is more appropriate.");
}
for (Propagator propagator : c.getPropagators()) {
if (settings.debugPropagation()) {
IPropagationEngine.Trace.printFirstPropagation(propagator, settings.outputWithANSIColors());
}
PropagationTrigger.execute(propagator, getSolver().getEngine());
}
}
}
/**
* Remove permanently the constraint c
from the constraint network.
*
* @param constraints the constraints to remove
* @throws SolverException if a constraint is unknown from the model
*/
public void unpost(Constraint... constraints) throws SolverException {
if (constraints != null) {
for (Constraint c : constraints) {
// 1. look for the constraint c
int idx = c.getCidxInModel();
c.declareAs(Constraint.Status.FREE, -1);
// 2. remove it from the network
Constraint cm = cstrs[--cIdx];
if (idx < cIdx) {
cstrs[idx] = cm;
cstrs[idx].declareAs(Constraint.Status.FREE, -1); // needed, to avoid throwing an exception
cstrs[idx].declareAs(Constraint.Status.POSTED, idx);
}
cstrs[cIdx] = null;
// 3. check if the resolution already started -> if true, dynamic deletion
IPropagationEngine engine = getSolver().getEngine();
if (engine != NoPropagationEngine.SINGLETON && engine.isInitialized()) {
engine.dynamicDeletion(c.getPropagators());
}
// 4. remove the propagators of the constraint from its variables
for (Propagator prop : c.getPropagators()) {
for (int v = 0; v < prop.getNbVars(); v++) {
prop.getVar(v).unlink(prop, v);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////// RELATED TO I/O ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Return a string describing the CSP defined in this
model.
*/
@Override
public String toString() {
StringBuilder st = new StringBuilder(256);
st.append(String.format("\n Model[%s]\n", name));
st.append(String.format("\n[ %d vars -- %d cstrs ]\n", vIdx, cIdx));
st.append(String.format("Feasability: %s\n", getSolver().isFeasible()));
st.append("== variables ==\n");
for (int v = 0; v < vIdx; v++) {
st.append(vars[v].toString()).append('\n');
}
st.append("== constraints ==\n");
for (int c = 0; c < cIdx; c++) {
st.append(cstrs[c].toString()).append('\n');
}
return st.toString();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////// RELATED TO IBEX ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the ibex reference
* Creates one if none
*
* @return the ibex reference
*/
public Ibex getIbex() {
if (ibex == null) {
try {
ibex = new Ibex();
} catch (ExceptionInInitializerError ini) {
throw new SolverException("Choco cannot initialize Ibex.\n" +
"The following option should be passed as VM argument: \"-Djava.library.path=/path/to/ibex/dynlib\"");
}
}
return ibex;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////// RELATED TO MODELING FACTORIES /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Model _me() {
return this;
}
}