/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package csp.backends;
import csp.datatypes.CSPProblem;
import csp.datatypes.CSPSolution;
import csp.datatypes.Constraint;
import csp.datatypes.IntegerBinaryExpression;
import csp.datatypes.IntegerConstant;
import csp.datatypes.IntegerDomainVariable;
import csp.datatypes.IntegerEnumeratedVariable;
import csp.datatypes.IntegerExpressionVisitor;
import csp.datatypes.IntegerIfThenElseExpression;
import csp.datatypes.IntegerVariable;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
/**
*
* @author Jeroen Janssen <Jeroen.Janssen@vub.ac.be>
*/
public class TailorSolver {
// TODO: this class needs massive cleaning!!
private String minionPath;
private File temp;
// We need to keep track of the outputted variables as minion only outputs
// Sol: value in the order in which the variables were created, without
// naming the variable in its output.
private ArrayList<IntegerDomainVariable> dVars;
private ArrayList<IntegerEnumeratedVariable> eVars;
// Since tailor doesn't let us use variables of the forn near(a,t1,t2)
// because the variable contains ( ) we will have to map our variables
// back and forth between the tailor notation and the CSP notation ...
private HashMap<String,String> varMap;
private HashMap<String,String> revVarMap;
// This counts the next free variable number in the tailor representation
private int varCounter = 0;
// The string we will use for variables in the tailor notation
private String varString = "v";
private int timeout = 1000;
private boolean foundSolution = false;
private static final String NL = System.getProperty("line.separator");
private String tailorPath;
// We need auxiliary variables to implement if-then-else constraints
private static String dummyVar = "FURETAUX";
private ArrayList<IntegerDomainVariable> dummyDomainVars;
private ArrayList<IntegerEnumeratedVariable> dummyEnumeratedVars;
private int dummyCounter = 0;
// Some constants for new domain dummy variables
private static int lowestVal = -999999999;
private static int highestVal = 999999999;
public static enum PPLEVEL {
NONE, GAC, SACBounds,
SAC, SSACBounds, SSAC
}
public static PPLEVEL PPLEVELNUMBER = PPLEVEL.NONE;
public static enum VARORDER {
NONE, SDF, SDF_RANDOM, SRF, SRF_RANDOM, LDF,
LDF_RANDOM, RANDOM, STATIC;
}
public static VARORDER VARORDERNUMBER = VARORDER.NONE;
public TailorSolver (String tailorPath, String minionPath) {
this.tailorPath = tailorPath;
this.minionPath = minionPath;
dummyDomainVars = new ArrayList<IntegerDomainVariable>();
dummyEnumeratedVars = new ArrayList<IntegerEnumeratedVariable>();
varMap = new HashMap<String,String>();
revVarMap = new HashMap<String,String>();
}
public void read(CSPProblem model) {
try {
temp = File.createTempFile("furet", ".eprime");
// To make sure we don't get a string index error from tailor *sigh*
System.err.println("Temp File created at: " + temp.getAbsolutePath());
// TODO DEBUG
//temp.deleteOnExit();
BufferedWriter out = new BufferedWriter(new FileWriter(temp));
// We only know how many aux vars we need after outputting all constraints
// so we'll have to keep two stringbuffers: one for the part of the file
// before the constraints and one for the part with the constraints ...
StringBuffer varBuff = new StringBuffer ();
//out.write("language ESSENCE' 1.b.a" + NL);
//out.newLine();
varBuff.append("language ESSENCE' 1.b.a" + NL);
varBuff.append(NL);
// TODO: let minion output the variable names as well so we can
// parse them more easily
dVars = new ArrayList<IntegerDomainVariable>();
// for (IntegerDomainVariable dVar : model.getDomainVariables()) {
// dVars.add(dVar);
// out.write("find " + dVar.getName() + " : int(" + dVar.getLowerBound()
// + ".." + dVar.getUpperBound() + ")" + NL);
// }
for (IntegerDomainVariable dVar : model.getDomainVariables()) {
String varName = varString+(varCounter++);
dVars.add(dVar);
varMap.put(dVar.getName(), varName);
revVarMap.put(varName,dVar.getName());
varBuff.append("find " + varName + " : int(" + dVar.getLowerBound()
+ ".." + dVar.getUpperBound() + ")" + NL);
}
//HashSet<MinionEnumeratedVariable> eVars = model.getEnumeratedVariables();
eVars = new ArrayList<IntegerEnumeratedVariable>();
// for (IntegerEnumeratedVariable eVar : model.getEnumeratedVariables()) {
// eVars.add(eVar);
// out.write("find " + eVar.getName() + " : int(");
// Iterator<Integer> it = eVar.getValues().iterator();
// if (it.hasNext()) {
// out.write(it.next().toString());
// }
// while (it.hasNext()) {
// out.write("," + it.next());
// }
// out.write(")" + NL);
// }
for (IntegerEnumeratedVariable eVar : model.getEnumeratedVariables()) {
String varName = varString + (varCounter++);
eVars.add(eVar);
varMap.put(eVar.getName(),varName);
revVarMap.put(varName,eVar.getName());
varBuff.append("find " + varName + " : int(");
Iterator<Integer> it = eVar.getValues().iterator();
if (it.hasNext()) {
varBuff.append(it.next().toString());
}
while (it.hasNext()) {
varBuff.append("," + it.next());
}
varBuff.append(")" + NL);
}
//out.newLine();
StringBuffer constrBuff = new StringBuffer ();
// out.write("such that" + NL);
// out.newLine();
//
// Iterator<Constraint> cit = model.getConstraints().iterator();
//
// if (cit.hasNext()) {
// outputConstraint(out, cit.next());
// }
// while (cit.hasNext()) {
// out.write("," + NL);
// outputConstraint(out, cit.next());
// }
constrBuff.append("such that" + NL);
constrBuff.append(NL);
Iterator<Constraint> cit = model.getConstraints().iterator();
if (cit.hasNext()) {
outputConstraint(constrBuff, cit.next());
}
while (cit.hasNext()) {
constrBuff.append("," + NL);
outputConstraint(constrBuff, cit.next());
}
for (IntegerDomainVariable dVar : dummyDomainVars) {
varBuff.append("find " + dVar.getName() + " : int(" + dVar.getLowerBound()
+ ".." + dVar.getUpperBound() + ")" + NL);
}
for (IntegerEnumeratedVariable eVar : dummyEnumeratedVars) {
varBuff.append("find " + eVar.getName() + " : int(");
Iterator<Integer> it = eVar.getValues().iterator();
if (it.hasNext()) {
varBuff.append(it.next().toString());
}
while (it.hasNext()) {
varBuff.append("," + it.next());
}
varBuff.append(")" + NL);
}
out.write(varBuff.toString() + NL + constrBuff.toString() + NL);
out.close();
} catch (IOException e) {
System.err.println("Could not create temp file for solving: " + e);
}
}
// TODO: remove this function and use constraintToTailor
// private void outputConstraint(BufferedWriter out, Constraint c)
// throws IOException {
private void outputConstraint(StringBuffer buff, Constraint c) {
// Constraint.Operator op = c.getOperator();
// //System.out.println("Operator: " + op);
// TailorOutputter mOutput = new TailorOutputter();
// String lhs = c.getLHS().accept(mOutput);
// String rhs = c.getRHS().accept(mOutput);
// if (op.equals(Constraint.Operator.GEQ)) {
// out.write(lhs + " >= " + rhs);
// } else if (op.equals(Constraint.Operator.LEQ)) {
// out.write(lhs + " <= " + rhs);
// } else if (op.equals(Constraint.Operator.EQ)) {
// out.write(lhs + " = " + rhs);
// } else {
// System.err.println("Unknown operator: " + op);
// }
buff.append(constraintToTailor(c));
}
private String constraintToTailor (Constraint c) {
StringBuffer out = new StringBuffer();
Constraint.Operator op = c.getOperator();
//System.out.println("Operator: " + op);
TailorOutputter mOutput = new TailorOutputter();
String lhs = c.getLHS().accept(mOutput);
String rhs = c.getRHS().accept(mOutput);
if (op.equals(Constraint.Operator.GEQ)) {
out.append(lhs + " >= " + rhs);
} else if (op.equals(Constraint.Operator.LEQ)) {
out.append(lhs + " <= " + rhs);
} else if (op.equals(Constraint.Operator.EQ)) {
out.append(lhs + " = " + rhs);
} else {
System.err.println("Unknown operator: " + op);
}
if(!mOutput.getExtraConstraints().isEmpty()) {
out.append("," + NL);
Iterator<String> it = mOutput.getExtraConstraints().iterator();
out.append(it.next());
while(it.hasNext()) {
out.append("," + NL);
out.append(it.next());
}
}
return out.toString();
}
public CSPSolution solve() {
CSPSolution model = null;
try {
// Tailor is quite picky over where the flags can occur
// so -out FILENAME must be up front!!
System.out.println("start taylor");
Process tailor = Runtime.getRuntime().exec(
"java -jar "
+ tailorPath + " "
+ "-out " + temp.getAbsolutePath() + ".minion"
+ " " + temp.getAbsolutePath());
// Let's wait for tailor to exit
// this while loop is apparently necessary, because otherwise
// the JVM will not wait for tailor to end before calling minion
String line;
BufferedReader tailorOutput =
new BufferedReader(new InputStreamReader(tailor.getInputStream()));
while ((line = tailorOutput.readLine()) != null) {
//System.err.println(line);
}
tailorOutput.close();
String minionString = minionPath + " -timelimit " + timeout + " "
+ "-noresume " + getPreprocessString() + " " + getVarorderString()
+ " " + temp.getAbsolutePath() + ".minion";
System.out.println("Running minion with: " + minionString);
Process p = Runtime.getRuntime().exec(minionString);
// TODO: killer does not work in netbeans. Does it work on CLI?
Runtime.getRuntime().addShutdownHook(new Thread(new MinionKiller(p)));
BufferedReader input =
new BufferedReader(new InputStreamReader(p.getInputStream()));
model = parseMinionOutput(input);
/*while ((line = input.readLine()) != null) {
System.out.println(line);
}*/
// remove temporary minion file on quit
//new File(temp.getAbsolutePath() + ".minion").deleteOnExit();
input.close();
} catch (Exception err) {
System.err.println("TailorSolver could not solve the problem:");
err.printStackTrace();
}
//temp.delete();
return model;
}
private CSPSolution parseMinionOutput(BufferedReader reader) {
CSPSolution model = new CSPSolution();
boolean hasSolution = false;
int i = 0;
int j = 0;
String line;
String solutionPrefix = "Sol: ";
String hasSolutionPrefix = "Problem solvable?: ";
try {
while ((line = reader.readLine()) != null) {
//System.err.println(line);
if (line.startsWith(solutionPrefix)) {
hasSolution = true;
Integer value = Integer.parseInt(line.trim().substring(solutionPrefix.length()));
String variable = "";
if (i < dVars.size()) {
variable = dVars.get(i++).getName();
model.addVariable(variable, value);
} else if (j < eVars.size()) {
variable = eVars.get(j++).getName();
model.addVariable(variable, value);
}
} else if (line.startsWith(hasSolutionPrefix)) {
String solStatus = line.trim().substring(hasSolutionPrefix.length());
if(solStatus.equals("yes")) {
hasSolution = true;
} else {
hasSolution = false;
}
}
}
if (!hasSolution) {
return null;
} else {
return model;
}
} catch (Exception err) {
System.err.println("Error parsing minion output.");
err.printStackTrace();
return null;
}
}
public boolean isFeasible() {
return foundSolution;
}
public void setTimeout(int timeoutSec) {
this.timeout = timeoutSec;
}
private String getPreprocessString() {
if (PPLEVELNUMBER.equals(PPLEVEL.NONE))
return "";
else
return "-preprocess " + PPLEVELNUMBER.name();
}
private String getVarorderString() {
// Code assumes the varorders are saved ass SDF_RANDOM if the minion input
// should be sdf-random etc.
if (VARORDERNUMBER.equals(VARORDER.NONE))
return "";
else
return "-varorder " + VARORDERNUMBER.name().toLowerCase().replaceAll("_", "-");
}
private class MinionKiller implements Runnable {
private Process minionProcess;
public MinionKiller(Process minonProcess) {
this.minionProcess = minionProcess;
}
public void run() {
//System.err.println("Killing minion ...");
if (minionProcess != null) {
minionProcess.destroy();
}
//System.err.println("Killed minion!");
}
}
private class TailorOutputter implements IntegerExpressionVisitor<String> {
// needed for the dummy variables
ArrayList<String> extraConstraints;
public TailorOutputter () {
extraConstraints = new ArrayList<String>();
}
@Override
public String visitBinaryExpression(IntegerBinaryExpression exp) {
String lhs = exp.getLHS().accept(this);
String rhs = exp.getRHS().accept(this);
StringBuffer buff = new StringBuffer();
if (exp.getOperator().equals(IntegerBinaryExpression.Operator.MAX)) {
buff.append("max(");
buff.append(lhs);
buff.append(",");
buff.append(rhs);
buff.append(")");
} else if (exp.getOperator().equals(IntegerBinaryExpression.Operator.MULT)) {
buff.append("(");
buff.append(lhs);
//buff.append(")");
buff.append(" * ");
//buff.append("(");
buff.append(rhs);
buff.append(")");
} else if (exp.getOperator().equals(IntegerBinaryExpression.Operator.SUM)) {
buff.append("(");
buff.append(lhs);
//buff.append(")");
buff.append(" + ");
//buff.append("(");
buff.append(rhs);
buff.append(")");
}
return buff.toString();
}
@Override
public String visitConstant(IntegerConstant c) {
return Integer.toString(c.getConstant());
}
@Override
public String visitDomainVariable(IntegerDomainVariable v) {
return varMap.get(v.getName());
}
@Override
public String visitEnumeratedVariable(IntegerEnumeratedVariable v) {
return varMap.get(v.getName());
}
@Override
public String visitIfThenElseExpression(IntegerIfThenElseExpression exp) {
// Small optimization
// TODO quite hacky though with the instanceof
String auxName;
if(exp.getTrueExpression() instanceof IntegerConstant
&& exp.getFalseExpression() instanceof IntegerConstant) {
Integer intTrue = ((IntegerConstant)exp.getTrueExpression()).getConstant();
Integer intFalse = ((IntegerConstant)exp.getFalseExpression()).getConstant();
ArrayList<Integer> values = new ArrayList<Integer>();
values.add(intTrue);
values.add(intFalse);
Collections.sort(values);
IntegerEnumeratedVariable aux = getNewEnumeratedDummy(values);
dummyEnumeratedVars.add(aux);
auxName = aux.getName();
} else {
IntegerDomainVariable aux = getNewDummyVariable(lowestVal,highestVal);
dummyDomainVars.add(aux);
auxName = aux.getName();
}
String cond = constraintToTailor(exp.getCondition());
String expTrue = exp.getTrueExpression().accept(this);
String expFalse = exp.getFalseExpression().accept(this);
StringBuffer buff = new StringBuffer ();
extraConstraints.add("(" + cond + ")" + " => " + "(" + auxName + "=" + expTrue + ")");
extraConstraints.add("!" + "(" + cond + ")" + " => " + "(" + auxName + "=" + expFalse + ")");
return auxName;
}
private IntegerEnumeratedVariable getNewEnumeratedDummy(ArrayList<Integer> values) {
return new IntegerEnumeratedVariable(dummyVar+(dummyCounter++),values);
}
private IntegerDomainVariable getNewDummyVariable(int lowerBound, int upperBound) {
return new IntegerDomainVariable(dummyVar+(dummyCounter++),lowerBound,upperBound);
}
public ArrayList<String> getExtraConstraints() {
return extraConstraints;
}
}
}