/**
* Author: Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*/
package at.iaik.suraq.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import at.iaik.suraq.main.SuraqOptions;
import at.iaik.suraq.smtlib.formula.AndFormula;
import at.iaik.suraq.smtlib.formula.Formula;
import at.iaik.suraq.smtlib.formula.NotFormula;
import at.iaik.suraq.smtlib.formula.PropositionalConstant;
/**
*
* Simplifies formulas using external tools (abc).
*
* @author Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*
*/
public class FormulaSimplifier {
public static final String SIMPLIFIER_PATH = "./lib/simplifier/simplify.sh";
@SuppressWarnings("unused")
private static final BigInteger CHECK_TRESHOLD = new BigInteger("10000");
/**
* The originalFormula, unsimplified formula.
*/
private final Formula originalFormula;
/**
* The simplified formula, or <code>null</code> if simplification was not
* successfully performed (yet).
*/
private Formula simplifiedFormula;
/**
* The encoding of literals.
*/
private Map<Formula, Integer> literalEncoding = new HashMap<Formula, Integer>();
public FormulaSimplifier(Formula original) {
assert (original != null);
this.originalFormula = original;
}
/**
* Simplifies the formula.
*
* @throws IOException
*
*/
public void simplify() throws IOException {
Util.printToSystemOutWithWallClockTimePrefix("Simplifying a formula with abc.");
File originalFile = File.createTempFile("originalFormula", ".aag",
new File("./"));
if (!SuraqOptions.getInstance().getKeepTemFiles())
originalFile.deleteOnExit();
File resultFile = File.createTempFile("simplifiedFormula", ".aag",
new File("./"));
if (!SuraqOptions.getInstance().getKeepTemFiles())
resultFile.deleteOnExit();
Util.printToSystemOutWithWallClockTimePrefix("Temporary aiger file for original formula: "
+ originalFile.toString());
writeToFile(originalFile);
Util.printToSystemOutWithWallClockTimePrefix("Starting abc.");
ProcessResult result = ProcessUtil
.runExternalProcess(
FormulaSimplifier.SIMPLIFIER_PATH + " "
+ originalFile.toString() + " "
+ resultFile.toString(), "");
if (result.getExitCode() != 0)
throw new RuntimeException("Error during abc simplification");
Util.printToSystemOutWithWallClockTimePrefix("Parsing abc results from aiger file "
+ resultFile.toString());
simplifiedFormula = parseResult(resultFile);
Util.printToSystemOutWithWallClockTimePrefix("Done with simplification");
}
/**
* @param originalFile
* @throws IOException
*/
private void writeToFile(File originalFile) throws IOException {
Set<Formula> literals = new HashSet<Formula>();
Set<Formula> done = new HashSet<Formula>();
originalFormula.getLiterals(literals, done);
TreeMap<Integer, Integer[]> aigNodes = new TreeMap<Integer, Integer[]>();
int count = 2;
for (Formula literal : literals) {
literalEncoding.put(literal, count);
count += 2;
}
Map<Formula, Integer> aigEncoding = new HashMap<Formula, Integer>(
literalEncoding);
originalFormula.toAig(aigNodes, aigEncoding);
FileWriter fwriter = new FileWriter(originalFile);
BufferedWriter writer = new BufferedWriter(fwriter);
writer.write("aag ");
writer.write(Integer.toString(aigNodes.descendingKeySet().iterator()
.next() / 2)); // Max variable index
writer.write(" ");
writer.write(Integer.toString(literals.size())); // Num inputs
writer.write(" 0 1 "); // Num latches & num outputs
writer.write(Integer.toString(aigNodes.size())); // Num AND gates
writer.write("\n");
// Inputs
for (Formula literal : literals) {
Integer aigVariable = aigEncoding.get(literal);
writer.write(aigVariable.toString());
writer.write("\n");
}
// Output
writer.write(aigEncoding.get(originalFormula).toString());
writer.write("\n");
// AND gates
for (Integer gate : aigNodes.keySet()) {
writer.write(gate.toString());
writer.write(" ");
Integer[] rhs = aigNodes.get(gate);
assert (rhs.length == 2);
writer.write(rhs[0].toString());
writer.write(" ");
writer.write(rhs[1].toString());
writer.write("\n");
}
// Symbol table
count = 0;
for (Formula literal : literals) {
writer.write("i");
writer.write(Integer.toString(count));
writer.write(" ");
writer.write(literal.toString().replaceAll("\\s{2,}", " ")
.replace("\n", ""));
writer.write("\n");
count++;
}
writer.close();
fwriter.close();
}
/**
* Parses the result of simplification from the given file.
*
* @param resultFile
* @throws IOException
*/
private Formula parseResult(File resultFile) throws IOException {
FileReader freader = new FileReader(resultFile);
BufferedReader reader = new BufferedReader(freader);
String[] firstLine = reader.readLine().split(" ");
assert (firstLine.length == 6);
assert (firstLine[0].equals("aag"));
int maxVarIndex = Integer.parseInt(firstLine[1]);
int numInputs = Integer.parseInt(firstLine[2]);
int numLatches = Integer.parseInt(firstLine[3]);
int numOutputs = Integer.parseInt(firstLine[4]);
int numAndGates = Integer.parseInt(firstLine[5]);
assert (numLatches == 0);
assert (numOutputs == 1);
assert (numInputs == literalEncoding.size());
assert (maxVarIndex >= numInputs + numLatches + numAndGates);
// Skip lines defining inputs
for (int count = 0; count < numInputs; count++)
reader.readLine();
String outputLine = reader.readLine();
int outputLiteral = Integer.parseInt(outputLine);
Map<Integer, Integer[]> andGates = new TreeMap<Integer, Integer[]>();
for (int count = 0; count < numAndGates; count++) {
String line = reader.readLine();
String[] gateDef = line.split(" ");
assert (gateDef.length == 3);
Integer[] rhs = { Integer.parseInt(gateDef[1]),
Integer.parseInt(gateDef[2]) };
andGates.put(Integer.parseInt(gateDef[0]), rhs);
}
reader.close();
freader.close();
Map<Integer, Formula> done = new HashMap<Integer, Formula>();
for (Formula literal : literalEncoding.keySet()) {
Integer aigLit = literalEncoding.get(literal);
done.put(aigLit, literal);
}
return aigToFormula(outputLiteral, andGates, done);
}
/**
* Returns the formula corresponding the the given <code>aigLit</code>.
*
* @param aigLit
* @param andGates
* @param done
* @return
*/
private Formula aigToFormula(int aigLit, Map<Integer, Integer[]> andGates,
Map<Integer, Formula> done) {
if (done.get(aigLit) != null)
return done.get(aigLit);
if (aigLit == 0)
return PropositionalConstant.create(false);
if (aigLit == 1)
return PropositionalConstant.create(true);
boolean negated = aigLit % 2 == 1;
int posAigLit = aigLit & 0xFFFFFFFE;
if (done.get(posAigLit) != null) {
assert (negated);
Formula result = NotFormula.create(done.get(posAigLit));
done.put(aigLit, result);
return result;
}
Integer[] gate = andGates.get(posAigLit);
assert (gate.length == 2);
Formula firstConjunct = aigToFormula(gate[0], andGates, done);
Formula secondConjunct = aigToFormula(gate[1], andGates, done);
List<Formula> conjuncts = new ArrayList<Formula>(2);
conjuncts.add(firstConjunct);
conjuncts.add(secondConjunct);
Formula andFormula = AndFormula.generate(conjuncts);
Formula result = negated ? NotFormula.create(andFormula) : andFormula;
done.put(aigLit, result);
return result;
}
/**
* @return the <code>simplifiedFormula</code>
*/
public Formula getSimplifiedFormula() {
return simplifiedFormula;
}
/**
* @return the <code>originalFormula</code>
*/
public Formula getOriginalFormula() {
return originalFormula;
}
/**
* Performs check of equivalence between original and simplified formula.
* This might be infeasible for large (original) formulas.
*
* @return <code>true</code> iff the original formula is equivalent to the
* simplified formula.
*/
public boolean checkSimplification() {
// BigInteger size = originalFormula.size(true,
// new HashMap<Formula, BigInteger>());
// if (size.compareTo(FormulaSimplifier.CHECK_TRESHOLD) < 0) {
// Util.printToSystemOutWithWallClockTimePrefix("WARNING: Skipped check of simplification because original formula has tree-size "
// + Util.largeNumberFormatter.format(size));
// return true;
// }
return Util.checkEquivalenceOfFormulas(originalFormula,
simplifiedFormula);
}
/**
* Writes the original formula to an AIGER file and reads it back in. This
* method servers purely testing purposes and has no practical significance.
* (That's why it is marked Deprecated.)
*
* @return <code>true</code> iff the formula that was read back in equals
* the original formula.
* @throws IOException
*/
@Deprecated
public boolean checkWriteReadOriginalFormula() throws IOException {
File testFile = File.createTempFile("testAigWriteRead", ".aag",
new File("./"));
if (!SuraqOptions.getInstance().getKeepTemFiles())
testFile.deleteOnExit();
writeToFile(testFile);
Formula testFormula = parseResult(testFile);
return Util.checkEquivalenceOfFormulas(originalFormula, testFormula);
}
}