package kodkod.engine.satlab;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
/**
* Runs an external version of minisat (whichever "minisat" is found in the PATH)
*
* @author aleks
*/
public class MiniSatExternal implements SATSolver {
private File cnfFile;
private OutputStream cnfOut;
private int vars = 0;
private int clauses = 0;
private boolean freed = false;
private boolean sat = false;
private boolean[] model;
public MiniSatExternal() {
init();
}
private void init() {
try {
cnfFile = File.createTempFile("kk_minisat", ".cnf");
cnfOut = new BufferedOutputStream(new FileOutputStream(cnfFile));
} catch (IOException e) {
throw new RuntimeException("Could not create cnf file: " + cnfFile.getAbsolutePath(), e);
}
}
@Override
public boolean addClause(int[] lits) {
try {
clauses++;
StringBuilder sb = new StringBuilder();
for (int l : lits) sb.append(l + " ");
sb.append("0\n");
cnfOut.write(sb.toString().getBytes());
return false;
} catch (IOException e) {
throw new RuntimeException("Could not write to cnf file " + cnfFile.getAbsolutePath(), e);
}
}
@Override
public void addVariables(int numVars) {
assert numVars >= 0;
vars += numVars;
}
@Override
public void free() {
try {
if (!freed) {
cnfOut.flush();
cnfOut.close();
cnfFile.deleteOnExit();
freed = true;
}
} catch (IOException e) {
throw new RuntimeException("Could not close cnf output stream");
}
}
@Override
public int numberOfClauses() {
return clauses;
}
@Override
public int numberOfVariables() {
return vars;
}
@Override
public boolean solve() throws SATAbortedException {
finalizeCnf();
File modelFile = runMinisat();
readModel(modelFile);
if (sat)
assert checkModel();
return sat;
}
private boolean checkModel() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(cnfFile)));
String line = br.readLine(); //header
line = br.readLine();
while (line != null) {
String[] clause = line.trim().split("\\ ");
int i;
for (i = 0; i < clause.length - 1; i++) {
int lit = Integer.parseInt(clause[i]);
if ((lit > 0 ? valueOf(lit) : !valueOf(Math.abs(lit))))
break;
}
if (i == clause.length - 1)
return false;
line = br.readLine();
}
return true;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (br != null) br.close();
} catch (IOException e) {
}
}
}
@Override
public boolean valueOf(int variable) {
if (!sat)
throw new RuntimeException("UNSAT");
return model[variable-1];
}
private void finalizeCnf() {
try {
free();
BufferedInputStream in = new BufferedInputStream(new FileInputStream(cnfFile));
File tmp = new File(cnfFile + ".tmp");
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tmp));
byte[] header = String.format("p cnf %s %s\n", vars, clauses).getBytes();
out.write(header);
int ch;
while ((ch = in.read()) != -1) {
out.write(ch);
}
in.close();
out.close();
tmp.renameTo(cnfFile);
} catch (IOException e) {
throw new RuntimeException("could not preprend header to cnf file: " + cnfFile.getAbsolutePath(), e);
}
}
private File runMinisat() {
try {
File modelFile = File.createTempFile("minisat_model", ".txt");
modelFile.deleteOnExit();
String cmd = String.format("minisat -verb=0 %s %s", cnfFile.getAbsolutePath(), modelFile.getAbsolutePath());
Process p = Runtime.getRuntime().exec(cmd);
int retVal = p.waitFor();
if (retVal == 0)
throw new RuntimeException("couldn't run shell command: " + cmd);
return modelFile;
} catch (Exception e) {
throw new RuntimeException("error during solving", e);
}
}
private void readModel(File modelFile) {
try {
BufferedInputStream in = new BufferedInputStream(new FileInputStream(modelFile));
String line = readLine(in).trim();
if (!"SAT".equals(line)) {
sat = false;
model = null;
return;
}
sat = true;
model = new boolean[vars];
int chCode;
int cnt = 0;
int curr = 0;
boolean sign = true;
while ((chCode = in.read()) != -1) {
char ch = (char)chCode;
switch (ch) {
case '-':
sign = false;
break;
case ' ': case '\n':
if (curr == 0)
break;
assert curr == cnt + 1;
model[cnt] = sign;
cnt++;
curr = 0;
sign = true;
break;
default:
int x = ch - '0';
assert x >= 0 && x <= 9;
curr = 10*curr + x;
break;
}
}
in.close();
} catch (IOException e) {
throw new RuntimeException("couldn't read model file: " + model);
}
}
private String readLine(InputStream in) throws IOException {
StringBuilder sb = new StringBuilder();
while (true) {
int ch = in.read();
if (ch == -1) break;
if (ch == '\n') break;
sb.append((char)ch);
}
return sb.toString();
}
}