org.chocosolver.solver.Solution Maven / Gradle / Ivy
/*
* 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 gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.chocosolver.memory.IEnvironment;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.propagation.PropagationEngine;
import org.chocosolver.solver.variables.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Class which stores the value of each variable in a solution
*
* @author Jean-Guillaume Fages
* @author Charles Prud'homme
* @since 05/06/2013
*/
public class Solution implements ICause {
//***********************************************************************************
// VARIABLES
//***********************************************************************************
/**
* No entry value for maps
*/
private static final int NO_ENTRY = Integer.MAX_VALUE;
// SOLUTION
/**
* Set to true when this object is empty
*/
private boolean empty;
/**
* Maps of value for integer variable (id - value)
*/
private TIntIntHashMap intmap;
/**
* Maps of value for real variable (id - value)
*/
private TIntObjectHashMap realmap;
/**
* Maps of value for set variable (id - values)
*/
private TIntObjectHashMap setmap;
// INPUT
/**
* Model to store
*/
private final Model model;
/**
* Variables to store;
*/
private Variable[] varsToStore;
//***********************************************************************************
// CONSTRUCTOR
//***********************************************************************************
/**
* Create an empty solution object able to store the value of each variable in
* varsToStore
when calling record()
*
* Stores all variables by default, when varsToStore
is empty
*
* @param model model of the solution
* @param varsToStore variables to store in this object
*/
public Solution(Model model, Variable... varsToStore) {
this.varsToStore = varsToStore;
empty = true;
this.model = model;
}
//***********************************************************************************
// METHODS
//***********************************************************************************
/**
* Records the current solution of the solver clears all previous recordings
*
* @return this object
*/
public Solution record() {
empty = false;
boolean warn = false;
if (varsToStore.length == 0) {
varsToStore = model.getVars();
}
assert varsToStore.length > 0;
if (intmap != null) {
intmap.clear();
}
if (realmap != null) {
realmap.clear();
}
if (setmap != null) {
setmap.clear();
}
for (Variable var : varsToStore) {
if ((var.getTypeAndKind() & Variable.TYPE) != Variable.CSTE) {
int kind = var.getTypeAndKind() & Variable.KIND;
if (var.isInstantiated()) {
switch (kind) {
case Variable.INT:
case Variable.BOOL:
if (intmap == null) {
intmap = new TIntIntHashMap(16, .5f, Solution.NO_ENTRY,
Solution.NO_ENTRY);
}
IntVar v = (IntVar) var;
intmap.put(v.getId(), v.getValue());
break;
case Variable.REAL:
if (realmap == null) {
realmap = new TIntObjectHashMap<>(16, 05f, Solution.NO_ENTRY);
}
RealVar r = (RealVar) var;
realmap.put(r.getId(), new double[]{r.getLB(), r.getUB()});
break;
case Variable.SET:
if (setmap == null) {
setmap = new TIntObjectHashMap<>(16, 05f, Solution.NO_ENTRY);
}
SetVar s = (SetVar) var;
setmap.put(s.getId(), s.getValue().toArray());
break;
default:
// do not throw exception to allow extending the solver with other variable kinds (e.g. graph)
// that should then be stored externally to this object
break;
}
} else {
warn = true;
}
}
}
if (warn && varsToStore[0].getModel().getSettings().warnUser()) {
model.getSolver().log().red().println("Some non decision variables are not instantiated in the current solution.");
}
return this;
}
@Override
public String toString() {
if (empty) {
return "Empty solution. No solution recorded yet";
}
StringBuilder st = new StringBuilder("Solution: ");
for (Variable var : varsToStore) {
if ((var.getTypeAndKind() & Variable.TYPE) != Variable.CSTE) {
int kind = var.getTypeAndKind() & Variable.KIND;
switch (kind) {
case Variable.INT:
case Variable.BOOL:
IntVar v = (IntVar) var;
st.append(v.getName()).append("=").append(intmap.get(v.getId()))
.append(", ");
break;
case Variable.REAL:
RealVar r = (RealVar) var;
double[] bounds = realmap.get(r.getId());
st.append(r.getName()).append("=[").append(bounds[0]).append(",")
.append(bounds[1]).append("], ");
break;
case Variable.SET:
SetVar s = (SetVar) var;
st.append(s.getName()).append("=")
.append(Arrays.toString(setmap.get(s.getId()))).append(", ");
break;
default:
// do not throw exception to allow extending the solver with other variable kinds (e.g. graph)
// that should then be stored externally to this object
break;
}
}
}
return st.toString();
}
public Solution copySolution() {
Solution ret = new Solution(model, varsToStore);
ret.empty = empty;
if (intmap != null) {
ret.intmap = new TIntIntHashMap(intmap);
}
if (realmap != null) {
ret.realmap = new TIntObjectHashMap<>(realmap);
}
if (setmap != null) {
ret.setmap = new TIntObjectHashMap<>(setmap);
}
return ret;
}
/**
* Get the value of variable v in this solution. If v was not instantiated during
* solution recording, calling this method will throw an exception.
*
* @param v IntVar (or BoolVar)
* @return the value of variable v in this solution, or null if the variable is not instantiated
* in the solution
* @throws SolverException if v was not instantiated during solution recording.
*/
public int getIntVal(IntVar v) {
if (empty) {
throw new SolverException("Cannot access value of " + v
+ ": No solution has been recorded yet (empty solution). Make sure this.record() has been called.");
}
if (intmap != null && intmap.containsKey(v.getId())) {
return intmap.get(v.getId());
} else {
if ((v.getTypeAndKind() & Variable.TYPE) == Variable.CSTE) {
return v.getValue();
} else {
throw new SolverException("Cannot access value of " + v
+ ": This variable has not been declared to be recorded in the Solution object (see Solution constructor).");
}
}
}
/**
* Set the value of variable v in this solution.
*
* @param var IntVar (or BoolVar)
* @param val its value
*/
public void setIntVal(IntVar var, int val) {
empty = false;
if (intmap == null) {
intmap = new TIntIntHashMap(16, .5f, Solution.NO_ENTRY, Solution.NO_ENTRY);
}
intmap.put(var.getId(), val);
}
/**
* Get the value of variable s in this solution. If v was not instantiated during
* solution recording, calling this method will throw an exception.
*
* @param s SetVar
* @return the value of variable s in this solution, or null if the variable is not instantiated
* in the solution.
* @throws SolverException if v was not instantiated during solution recording.
*/
public int[] getSetVal(SetVar s) {
if (empty) {
throw new SolverException("Cannot access value of " + s
+ ": No solution has been recorded yet (empty solution). Make sure this.record() has been called.");
}
if (setmap != null && setmap.containsKey(s.getId())) {
return setmap.get(s.getId());
} else if ((s.getTypeAndKind() & Variable.TYPE) == Variable.CSTE) {
return s.getValue().toArray();
} else {
throw new SolverException("Cannot access value of " + s
+ ": This variable has not been declared to be recorded in the Solution object (see Solution constructor).");
}
}
/**
* Set the value of variable v in this solution
*
* @param var SetVar
* @param val its value
*/
public void setSetVal(SetVar var, int[] val) {
empty = false;
if (setmap == null) {
setmap = new TIntObjectHashMap<>(16, 05f, Solution.NO_ENTRY);
}
setmap.put(var.getId(), val);
}
/**
* Get the bounds of r in this solution. If v was not instantiated during solution
* recording, calling this method will throw an exception.
*
* @param r RealVar
* @return the bounds of r in this solution, or null if the variable is not instantiated in the
* solution
* @throws SolverException if v was not instantiated during solution recording.
*/
public double[] getRealBounds(RealVar r) {
if (empty) {
throw new SolverException("Cannot access value of " + r
+ ": No solution has been recorded yet (empty solution). Make sure this.record() has been called.");
}
if (realmap != null && realmap.containsKey(r.getId())) {
return realmap.get(r.getId());
} else {
if ((r.getTypeAndKind() & Variable.TYPE) == Variable.CSTE) {
return new double[]{r.getLB(), r.getUB()};
} else {
throw new SolverException("Cannot access value of " + r
+ ": This variable has not been declared to be recorded in the Solution object (see Solution constructor).");
}
}
}
/**
* Set the value of variable v in this solution
*
* @param var RealVar
* @param val its value
*/
public void setRealBounds(RealVar var, double[] val) {
empty = false;
if (realmap == null) {
realmap = new TIntObjectHashMap<>(16, 05f, Solution.NO_ENTRY);
}
if (val.length != 2) {
throw new SolverException("wrong array size");
}
realmap.put(var.getId(), val);
}
/**
* Restore the solution in {@link #model}. Restoring a solution in a model consists in iterating
* over model's variables and forcing each of them to be instantiated to the value recorded in
* this solution.
*
* If a variable was not instantiated while this solution was recorded, then a {@link
* SolverException} will be thrown (indeed, forcing this instantiation will call {@link
* #getIntVal(IntVar)}, {@link #getSetVal(SetVar)} and/or {@link #getRealBounds(RealVar)}.
*
*
* When instantiating all variables to their value in the solution, a propagation loop will be
* achieved to ensure that the correctness and completeness of the model. If the propagation
* detects a failure, a {@link ContradictionException} will be thrown. If so, the propagation
* engine is not flushed automatically, and a call to {@link PropagationEngine#flush()} may be
* needed.
*
* However, the satisfaction of the solution status is not check (see {@link
* Settings#checkModel(Solver)} to check satisfaction).
*
*
* Restoring a solution is permanent except if a backtrack occurs. Note that, for a backtrack to
* be feasible, it needs to be anticipated, by calling {@link IEnvironment#worldPush()}:
*
*
* {@code
* // optional: for assertion only
* int wi = model.getEnvironment().getWorldIndex();
* // prepare future backtrack, in order to forget solution
* model.getEnvironment().worldPush();
* // restore the solution in `model`
* solution.restore();
* // ... do something
* // backtrack to before solution restoration
* model.getEnvironment().worldPop();
* // optional: for assertion only
* assert wi == model.getEnvironment().getWorldIndex();
* }
*
*
*
* @throws SolverException if a variable was not instantiated during solution recording.
* @throws ContradictionException if restoring the solution leads to failure
*/
public void restore() throws ContradictionException {
for (Variable var : varsToStore) {
if ((var.getTypeAndKind() & Variable.TYPE) != Variable.CSTE) {
int kind = var.getTypeAndKind() & Variable.KIND;
switch (kind) {
case Variable.INT:
case Variable.BOOL:
IntVar v = (IntVar) var;
v.instantiateTo(intmap.get(v.getId()), this);
break;
case Variable.REAL:
RealVar r = (RealVar) var;
double[] bounds = realmap.get(r.getId());
r.updateBounds(bounds[0], bounds[1], this);
break;
case Variable.SET:
SetVar s = (SetVar) var;
s.instantiateTo(setmap.get(s.getId()), this);
break;
default:
// do not throw exception to allow extending the solver with other variable kinds (e.g. graph)
// that should then be stored externally to this object
break;
}
}
}
model.getSolver().propagate();
}
/**
* @return true if a solution has been recorded into this, false otherwise.
*/
public boolean exists() {
return !empty;
}
/**
* Iterate over the variable of this
and build a list that contains all the {@link
* IntVar} of the solution.
* excludes {@link BoolVar} if includeBoolVar=false.
*
* @param includeBoolVar indicates whether or not to include {@link BoolVar}
* @return array of {@link IntVar} in this
solution
*/
public List retrieveIntVars(boolean includeBoolVar) {
List ivars = new ArrayList<>();
for (int i = 0; i < varsToStore.length; i++) {
int kind = (varsToStore[i].getTypeAndKind() & Variable.KIND);
if (kind == Variable.INT || (includeBoolVar && kind == Variable.BOOL)) {
ivars.add((IntVar) varsToStore[i]);
}
}
return ivars;
}
/**
* Iterate over the variable of this
and build a list that contains the {@link
* BoolVar} only.
*
* @return array of {@link BoolVar} in this
solution
*/
public List retrieveBoolVars() {
List bvars = new ArrayList<>();
int k = 0;
for (int i = 0; i < varsToStore.length; i++) {
if ((varsToStore[i].getTypeAndKind() & Variable.KIND) == Variable.BOOL) {
bvars.add((BoolVar) varsToStore[i]);
}
}
return bvars;
}
/**
* Iterate over the variable of this
and build a list that contains the {@link SetVar} only.
* It also contains FIXED variables and VIEWS, if any.
*
* @return array of SetVars in this
model
*/
public List retrieveSetVars() {
List svars = new ArrayList<>();
for (int i = 0; i < varsToStore.length; i++) {
if ((varsToStore[i].getTypeAndKind() & Variable.KIND) == Variable.SET) {
svars.add((SetVar) varsToStore[i]);
}
}
return svars;
}
/**
* Iterate over the variable of this
and build a list that contains the {@link RealVar} only.
*
* @return array of {@link RealVar} in this
solution
*/
public List retrieveRealVars() {
List rvars = new ArrayList<>();
for (int i = 0; i < varsToStore.length; i++) {
if ((varsToStore[i].getTypeAndKind() & Variable.KIND) == Variable.REAL) {
rvars.add((RealVar) varsToStore[i]);
}
}
return rvars;
}
}