/*
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package abs.frontend.mtvl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import abs.frontend.ast.BoundaryInt;
import abs.frontend.ast.BoundaryVal;
import abs.frontend.ast.Limit;
import abs.frontend.ast.Model;
import choco.Choco;
import choco.cp.model.CPModel;
import choco.cp.solver.CPSolver;
import choco.kernel.common.logging.ChocoLogging;
import choco.kernel.common.logging.Verbosity;
import choco.kernel.model.constraints.ComponentConstraint;
import choco.kernel.model.constraints.Constraint;
import choco.kernel.model.constraints.ConstraintType;
import choco.kernel.model.constraints.MetaConstraint;
import choco.kernel.model.variables.integer.IntegerExpressionVariable;
import choco.kernel.model.variables.integer.IntegerVariable;
import choco.kernel.solver.ContradictionException;
public class ChocoSolver {
public final CPModel cpmodel;
public final CPSolver solver;
public boolean solved = false;
public boolean newsol = false;
public final Map<String, IntegerVariable> vars;
public final Map<String, Integer> defaultvals;
// private boolean debug = false;
private List<Constraint> constraints = new ArrayList<Constraint>();
private Model absmodel = new Model();
public ChocoSolver() {
// Build the model
cpmodel = new CPModel();
solver = new CPSolver();
ChocoLogging.setVerbosity(Verbosity.SILENT);
vars = new HashMap<String, IntegerVariable>();
defaultvals = new HashMap<String, Integer>();
ChocoLogging.setVerbosity(Verbosity.OFF);
}
public ChocoSolver(Model m) {
this();
if (m.debug)
ChocoLogging.setVerbosity(Verbosity.DEFAULT);
absmodel = m;
}
/**
* add int variable
*
* @param name
* - name of the variable to be added
* @param from
* - lowerlimit of the domain of the variable
* @param to
* - upper limit of the domain of the variable
**/
public void addIntVar(String name, int from, int to) {
IntegerVariable v = Choco.makeIntVar(name, from, to);
// addConstraint(Choco.geq(v,from)); // needed to include the variable
// in the constraints to be solved.
vars.put(name, v);
defaultvals.put(name, from);
if (absmodel.debug)
absmodel.println(" adding Int var '" + name + "' (default -> " + from + ")");
cpmodel.addVariable(v); // needed to include the variable in the constraints
// to be solved.
}
public void addIntVar(String name, int fromto, boolean from) {
IntegerVariable v = Choco.makeIntVar(name);
if (from)
addConstraint(Choco.geq(v, fromto));
else
addConstraint(Choco.leq(v, fromto));
vars.put(name, v);
defaultvals.put(name, fromto);
if (absmodel.debug)
absmodel.println(" adding Int var '" + name + "' (default -> " + fromto + ")");
// m.addVariable(v); // not needed, since v is used in the constraints
}
public void addIntVar(String name, int[] vals) {
IntegerVariable v = Choco.makeIntVar(name, vals);
vars.put(name, v);
defaultvals.put(name, vals[0]); // vals has at least 1 element! (by the
// parser constraints)
if (absmodel.debug)
absmodel.println(" adding Int var '" + name + "' (default -> " + vals[0] + ")");
cpmodel.addVariable(v); // needed to include the variable in the constraints
// to be solved.
}
public void addIntVar(String name) {
IntegerVariable v = Choco.makeIntVar(name);
vars.put(name, v);
defaultvals.put(name, 0);
if (absmodel.debug)
absmodel.println(" adding Int var '" + name + "' (default -> 0)");
// m.addVariable(v); // not needed - if variable is not constrained in
// any way, it should not be considered when solving.
}
public void addBoolVar(String name) {
IntegerVariable v = Choco.makeBooleanVar(name);
vars.put(name, v);
defaultvals.put(name, 0);
if (absmodel.debug)
absmodel.println(" adding Bool var '" + name + "' (default -> False)");
// m.addVariable(v); // not needed - if variable is not constrained in
// any way, it should not be considered when solving.
}
public void addBoundedVar(String name, BoundaryInt b1, BoundaryInt b2) {
if (b1 instanceof Limit)
if (b2 instanceof Limit)
addIntVar(name);
else
addIntVar(name,((BoundaryVal) b2).getValue(), false);
else if (b2 instanceof Limit)
addIntVar(name,((BoundaryVal) b1).getValue(), true);
else
addIntVar(name, ((BoundaryVal) b1).getValue(), ((BoundaryVal) b2).getValue());
}
// Is this method ever called??
// The mTVL syntax does not seem to allow constraining an attribute to a given set.
public void addSetVar(String name, BoundaryInt[] bs) {
int bsize = bs.length - 1;
int[] vals = new int[bsize];
// addSetVar only called if bs has only BoundaryVals
for (int i=0; i < bsize; i++) {
vals[i] = ((BoundaryVal) bs[i+1]).getValue(); // drop first value - repeated
}
addIntVar(name,vals);
}
/** set a bool variable to true **/
public void forceTrue(String name) {
IntegerVariable v = Choco.makeIntVar(name, 1, 1);
vars.put(name, v);
defaultvals.put(name, 1);
cpmodel.addVariable(v);
}
/** add choco constraint **/
public void addConstraint(Constraint c) {
constraints.add(c);
// m.addConstraint(c);
}
public IntegerVariable getVar(String var) {
return vars.get(var);
}
public boolean solve() {
// add the constraints
if (!solved) {
for (Constraint c : constraints)
cpmodel.addConstraint(c);
}
// show the problem
if (absmodel.debug) {
absmodel.println("## The constraints:");
// ast.println(m.pretty());
for (Constraint c : constraints) {
if (!c.pretty().startsWith("true"))
absmodel.println(prettyConst(c));
}
absmodel.println("-----");
}
// Read the model
solver.read(cpmodel);
// Solve the model
newsol = solver.solve();
solved = true;
return newsol;
}
public boolean optimise(String var, Boolean minimise) {
// add the constraints
if (!solved) {
for (Constraint c : constraints)
cpmodel.addConstraint(c);
}
// Impose the feature name to be present
if (var.contains(".")) {
String feat = var.split("\\.")[0];
if (absmodel.debug)
absmodel.println("## including feature '" + feat + "'"); // and its
// parents.");
if (vars.containsKey(feat)) {
if (absmodel.debug)
absmodel.println(" " + feat + " (selected) -> 1");
cpmodel.addConstraint(Choco.eq(vars.get(feat), 1));
// // collect parents of 'newFeatures'
// Set<String> newFeatures = new HashSet<String>();
// Set<String> newParents = new HashSet<String>();
//
// if (model != null) {
// newFeatures.add(feat);
// model.collectParents(newFeatures,newParents);
// }
// // add newParents and default values to the solution
// Iterator<IntegerVariable> it = m.getIntVarIterator();
// while (it.hasNext()) { // for all variables in the
// constraints (model): round 2
// IntegerVariable var2 = it.next();
//
// // If it is a parent to include, set
// if (newParents.contains(var2.getName())) {
// if (ast.debug)
// ast.println(" "+var2+" (parent) -> 1");
// m.addConstraint(Choco.eq(var2, 1));
// }
// }
}
}
// show the problem
if (absmodel.debug) {
absmodel.println("## The constraints:");
// ast.println(m.pretty());
for (Constraint c : constraints) {
if (!c.pretty().startsWith("true"))
absmodel.println(prettyConst(c));
}
absmodel.println("-----");
}
// Read the model
solver.read(cpmodel);
// Minmise the model, if possible
if (vars.containsKey(var))
if (solver.contains(vars.get(var))) {
solved = true;
if (minimise)
newsol = solver.minimize(solver.getVar(vars.get(var)), true);
else
newsol = solver.maximize(solver.getVar(vars.get(var)), true);
return newsol;
}
return false;
}
public int countSolutions() {
if (absmodel.debug) {
absmodel.print("## The constraints:");
absmodel.println(cpmodel.pretty());
}
// add the constraints
for (Constraint c : constraints)
cpmodel.addConstraint(c);
// Read the model
solver.read(cpmodel);
// Solve the model
solver.solveAll();
return solver.getNbSolutions();
}
public boolean solveAgain() {
if (!solved)
newsol = solve();
else
newsol = solver.nextSolution();
return newsol;
}
public Map<String, Integer> getSolution() {
if (!solved)
solve();
HashMap<String, Integer> result = new HashMap<String, Integer>();
Iterator<IntegerVariable> it = cpmodel.getIntVarIterator();
while (it.hasNext()) {
IntegerVariable var = it.next();
result.put(var.getName(), solver.getVar(var).getVal());
}
return result;
}
public Set<Map<String,Integer>> getSolutions() {
Set<Map<String, Integer>> solutions = new HashSet<Map<String,Integer>>();
while(solveAgain()) {
Map<String,Integer> sol = new HashMap<String,Integer>();
Iterator<IntegerVariable> it = cpmodel.getIntVarIterator();
while (it.hasNext()) {
IntegerVariable var = it.next();
sol.put(var.getName(), solver.getVar(var).getVal());
}
solutions.add(sol);
}
return solutions;
}
public Set<Set<String>> getSolutionsFeaturesOnly() {
Set<Set<String>> solutions = new HashSet<Set<String>>();
while(solveAgain()) {
HashSet<String> sol = new HashSet<String>();
Iterator<IntegerVariable> it = cpmodel.getIntVarIterator();
while (it.hasNext()) {
IntegerVariable var = it.next();
if (solver.getVar(var).getVal() == 1) // We are dealing with features only, where 1 means TRUE
sol.add(var.getName());
}
solutions.add(sol);
}
return solutions;
}
public String resultToString() {
if (!solved)
solve();
if (!newsol)
return "-- No (more) solutions --\n";
StringBuilder result = new StringBuilder();
Iterator<IntegerVariable> it = cpmodel.getIntVarIterator();
while (it.hasNext()) {
IntegerVariable var = it.next();
if (absmodel.debug || !var.getName().startsWith("$"))
result.append(var.getName() + " -> " + solver.getVar(var).getVal() + "\n");
}
return result.toString();
}
public int maxValue(String optVar) {
if (!solved)
solve();
if (!newsol)
return 0;
Iterator<IntegerVariable> it = cpmodel.getIntVarIterator();
while (it.hasNext()) {
IntegerVariable var = it.next();
if (var.getName().equalsIgnoreCase(optVar)) {
return solver.getVar(var).getVal();
}
}
return 0;
}
public String minimiseToString(String var) {
optimise(var, true);
return resultToString();
}
public String maximiseToString(String var) {
if (absmodel.debug)
absmodel.println("optimising " + var);
optimise(var, false);
return resultToString();
}
public int maximiseToInt(String var) {
if (absmodel.debug)
absmodel.println("optimising " + var);
optimise(var, false);
return maxValue(var);
}
public boolean checkSolution(Map<String, Integer> solution) {
return checkSolution(solution, null);
}
// public boolean checkSolution(Map<String,Integer> solution, Model model) {
// boolean res = true;
// for (Constraint c: constraints) {
// CPModel m = new CPModel();
// m.addConstraint(c);
// boolean csol = checkSolution(solution,model,m);
// res = res & csol;
// //if (debug)
// if (!csol) ast.println("Constraint failed: "+prettyConst(c));
// }
//
// return res;
// for (Constraint c: constraints)
// m.addConstraint(c);
// return checkSolution(solution,model,m);
// }
public List<String> checkSolutionWithErrors(Map<String, Integer> solution, Model model) {
List<String> res = new ArrayList<String>();
// check first for limits of variables
for (IntegerVariable v : vars.values()) {
CPModel m = new CPModel();
m.addVariable(v);
if (!checkSolution(solution, model, m))
res.add(v.toString());
}
// now check all explicit constraints
for (Constraint c : constraints) {
CPModel m = new CPModel();
m.addConstraint(c);
if (!checkSolution(solution, model, m))
res.add(prettyConst(c));
}
return res;
}
public boolean checkSolution(Map<String, Integer> solution, Model model) {
List<String> errors = checkSolutionWithErrors(solution, model);
for (String s : errors)
absmodel.println("Constraint failed: " + s);
return errors.isEmpty();
}
// Adds parenthesis for readability if there are *spaces* in the string.
private static String mbParenthesis(String s) {
if (s.contains(" "))
return "(" + s + ")";
else
return s;
}
private static String prettyConst(Constraint c) {
// System.out.println( "["+c.getClass()+"] "+c.pretty());
if (c instanceof MetaConstraint) {
MetaConstraint<?> mc = (MetaConstraint<?>) c;
if (mc.getConstraintType() == ConstraintType.IMPLIES)
return mbParenthesis(prettyConst(mc.getConstraint(0))) + " -> "
+ mbParenthesis(prettyConst(mc.getConstraint(1)));
if (mc.getConstraintType() == ConstraintType.AND)
return mbParenthesis(prettyConst(mc.getConstraint(0))) + " /\\ "
+ mbParenthesis(prettyConst(mc.getConstraint(1)));
if (mc.getConstraintType() == ConstraintType.OR)
return mbParenthesis(prettyConst(mc.getConstraint(0))) + " \\/ "
+ mbParenthesis(prettyConst(mc.getConstraint(1)));
// output.println("I'm a imply!\nleft: "+mc.getConstraint(0).pretty());
}
if (c instanceof ComponentConstraint) {
ComponentConstraint cc = (ComponentConstraint) c;
// return cc.getVariable(0) + "[[]]";
if (c.getConstraintType() == ConstraintType.EQ)
// if (c.getConstraintType().getName().equals("eq")
if (c.pretty().endsWith("[0, 1], 1 } )"))
// cc.getVariable(0).getConstraint(1).pretty()=="1" &&
// mc.getConstraint(0).pretty().endsWith("[0, 1]"))
return cc.getVariable(0).getName();
else if (c.pretty().endsWith("[1, 1], 1 } )"))
// cc.getVariable(0).getConstraint(1).pretty()=="1" &&
// mc.getConstraint(0).pretty().endsWith("[0, 1]"))
return cc.getVariable(0).getName() + "[true]";
else
return prettyVar(cc.getVariable(0)) + " = " + prettyVar(cc.getVariable(1));
if (c.getConstraintType() == ConstraintType.GEQ)
return prettyVar(cc.getVariable(0)) + " >= " + prettyVar(cc.getVariable(1));
if (c.getConstraintType() == ConstraintType.LEQ)
return prettyVar(cc.getVariable(0)) + " <= " + prettyVar(cc.getVariable(1));
if (c.getConstraintType() == ConstraintType.GT)
return prettyVar(cc.getVariable(0)) + " > " + prettyVar(cc.getVariable(1));
if (c.getConstraintType() == ConstraintType.LT)
return prettyVar(cc.getVariable(0)) + " < " + prettyVar(cc.getVariable(1));
}
return // "["+c.getClass()+"] "+c.pretty();
c.pretty();
}
private static String prettyVar(choco.kernel.model.variables.Variable v) {
// System.out.println( "--["+v+": "+v.getClass()+"] "+v.pretty());
if (v instanceof IntegerExpressionVariable) {
IntegerExpressionVariable exp = (IntegerExpressionVariable) v;
if (exp.getOperator().name() == "SUM") {
String res = "";
if (exp.getNbVars() > 0) {
res += prettyVar(exp.getVariable(0));
for (int i = 1; i < exp.getNbVars(); i++)
res += " + " + prettyVar(exp.getVariable(i));
} else
res = "0";
return res;
}
}
if (v instanceof IntegerVariable) {
IntegerVariable iv = (IntegerVariable) v;
if (iv.isBoolean())
return iv.getName();
}
return v.pretty();
}
public boolean checkSolution(Map<String, Integer> solution, Model model, CPModel m) {
// Read the model
CPSolver s = new CPSolver();
s.read(m);
if (absmodel.debug)
absmodel.println("solution to check:\n" + solution);
// HashMap<String,Integer> selection = new HashMap<String,Integer>();
Iterator<IntegerVariable> it = m.getIntVarIterator();
try {
// aux variables
int val;
Set<String> newFeatures = new HashSet<String>();
Set<String> newParents = new HashSet<String>();
if (absmodel.debug)
absmodel.println("Adding new values:");
while (it.hasNext()) { // for all variables in the constraints
// (model): round 1
IntegerVariable var = it.next();
// IF used variable is present in the solution, update it!
if (solution.containsKey(var.getName())) {
val = solution.get(var.getName());
if (absmodel.debug)
absmodel.println(" " + var + " -> " + val);
s.getVar(var).setVal(val);
// Possible feature name -- include later the parents.
if (val == 1)
newFeatures.add(var.getName());
}
}
// add parents of features from the solution that are not in the
// constraints (model)
for (Map.Entry<String, Integer> entry : solution.entrySet()) {
if (entry.getValue() == 1)
if (!entry.getKey().contains("."))
newFeatures.add(entry.getKey());
}
// collect parents of 'newFeatures'
if (model != null)
model.collectParents(newFeatures, newParents);
// add newParents and default values to the solution
it = m.getIntVarIterator();
while (it.hasNext()) { // for all variables in the constraints
// (model): round 2
IntegerVariable var = it.next();
// If it is a parent to include, set
if (newParents.contains(var.getName())) {
if (absmodel.debug)
absmodel.println(" " + var + " (parent) -> 1");
s.getVar(var).setVal(1);
}
// ELSE use default value
else if (!solution.containsKey(var.getName())) {
// By default, the optional wrapper "$..." is ALWAYS true
if (var.getName().startsWith("$")) {
if (absmodel.debug)
absmodel.println(" " + var + " (default) -> 1");
s.getVar(var).setVal(1);
// By default, unrefered features & attributes are false
} else if (defaultvals.containsKey(var.getName())) {
int defval = defaultvals.get(var.getName());
if (absmodel.debug)
absmodel.println(" " + var.getName() + " (default) -> " + defval);
s.getVar(var).setVal(defval);
} else {
if (absmodel.debug)
absmodel.println(" " + var.getName() + " (default) -> 0");
s.getVar(var).setVal(0);
}
}
}
} catch (ContradictionException e1) {
if (absmodel.debug)
System.err.println("$$$ Contradiction found... $$$");
}
// Catch-all
catch (Exception e1) {
// Catch-all
if (absmodel.debug) {
System.err.println("$$$ Failed to check solution... $$$");
e1.printStackTrace();
}
}
// if (debug) {
// String result = "";
// it = m.getIntVarIterator();
// while (it.hasNext()) {
// IntegerVariable var = (IntegerVariable) it.next();
// result = result + var.getName() + " -> "+s.getVar(var).getVal() +
// "\n";
// }
// output.println("Trying:\n"+result);
// }
return s.checkSolution();
}
public static Constraint eqeq(IntegerVariable v1, IntegerVariable v2) {
return Choco.eq(v1, v2);
}
public static Constraint eqeq(IntegerExpressionVariable v1, IntegerExpressionVariable v2) {
return Choco.eq(v1, v2);
}
public static Constraint eqeq(IntegerExpressionVariable v1, int v2) {
return Choco.eq(v1, v2);
}
public static Constraint isTrue(IntegerExpressionVariable v1) {
return Choco.eq(v1, 1);
}
public CPSolver getCPSolver() {
return solver;
}
}