package splar.plugins.reasoners.sat.sat4j;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.sat4j.core.LiteralsUtils;
import org.sat4j.core.VecInt;
import org.sat4j.minisat.core.Solver;
import org.sat4j.specs.ISolver;
import org.sat4j.specs.IVecInt;
import splar.core.fm.FTTraversalNodeSelector;
import splar.core.fm.FTTraversals;
import splar.core.fm.FeatureGroup;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureTreeNode;
import splar.core.fm.SolitaireFeature;
public class FTReasoningWithSAT extends ReasoningWithSAT {
protected FeatureModel featureModel;
public FTReasoningWithSAT(String solverName, FeatureModel featureModel, int timeout) {
super(solverName, timeout);
this.featureModel = featureModel;
}
public FeatureModel getFeatureModel() {
return featureModel;
}
protected void updateVariableMappings() {
varIndex2NameMap = new String[featureModel.countNodes()];
for ( FeatureTreeNode node : featureModel.getNodes() ) {
if ( !(node instanceof FeatureGroup) ) {
String varName = node.getID();
Integer index = (Integer)node.getAttachedData();
varName2IndexMap.put(varName, index);
//System.out.println(varName + " = " + index);
varIndex2NameMap[index-1] = varName;
}
}
}
protected void addSolverClauses(ISolver solver) throws Exception {
// release all data attached to feature model nodes
featureModel.resetNodesAttachedData();
solver.newVar(featureModel.countNodes());
// count features (variables)
int countFeatures= 1;
// root is always TRUE
IVecInt vectInt = new VecInt(1);
vectInt.push(countFeatures);
solver.addClause(vectInt);
//System.out.println(vectInt);
FeatureTreeNode rootNode = featureModel.getRoot();
rootNode.attachData(new Integer(countFeatures));
Vector<FeatureTreeNode> nodes = new Vector<FeatureTreeNode>();
nodes.add(rootNode);
// Perform a Breadht First Traversal of the feature tree
while ( nodes.size() > 0 ) {
// consumes first node
FeatureTreeNode curNode = nodes.firstElement();
nodes.remove(curNode);
if ( curNode != null ) {
int parentVarID = 0;
int count = curNode.getChildCount();
if ( count > 0 ) {
for( int i = 0 ; i < count ; i++ ) {
FeatureTreeNode childNode = ((FeatureTreeNode)curNode.getChildAt(i));
int childVarID = 0;
// It's a solitaire feature
if ( childNode instanceof SolitaireFeature ) {
parentVarID = (Integer)curNode.getAttachedData();
childVarID = ++countFeatures;
childNode.attachData(childVarID);
nodes.add(childNode);
SolitaireFeature solitaireNode = (SolitaireFeature)childNode;
// if node is optional relation is "child implies parent"
vectInt = new VecInt(2);
vectInt.push(parentVarID);
vectInt.push(-childVarID);
solver.addClause(vectInt);
//System.out.println(vectInt);
// if mandatory, "parent also implies child"
if ( !solitaireNode.isOptional() ) {
vectInt = new VecInt(2);
vectInt.push(-parentVarID);
vectInt.push(childVarID);
solver.addClause(vectInt);
//System.out.println(vectInt);
}
}
// It's a feature group
else if ( childNode instanceof FeatureGroup ) {
FeatureGroup fgNode = (FeatureGroup)childNode;
parentVarID = (Integer)((FeatureTreeNode)fgNode.getParent()).getAttachedData();
int countGroupedNodes = fgNode.getChildCount();
// (not P or g1 or g2 or ... or gn)
vectInt = new VecInt(countGroupedNodes+1);
vectInt.push(-parentVarID);
// (not g1 or P) (not g2 or P) ... (not gn or P)
IVecInt vectIntGrpOR;
for( int j = 0 ; j < countGroupedNodes ; j++ ) {
FeatureTreeNode groupedNode = (FeatureTreeNode)fgNode.getChildAt(j);
childVarID = ++countFeatures;
groupedNode.attachData(childVarID);
nodes.add(groupedNode);
vectInt.push(childVarID);
vectIntGrpOR = new VecInt(2);
vectIntGrpOR.push(parentVarID);
vectIntGrpOR.push(-childVarID);
solver.addClause(vectIntGrpOR);
//System.out.println(vectIntGrpOR);
}
solver.addClause(vectInt);
//System.out.println(vectInt);
// if it's an exclusive-OR group
// (not g1 or not g2) (not g1 or not g3) (not g2 or not g3)
int min = fgNode.getMin();
int max = fgNode.getMax();
max = (max == -1 ? countGroupedNodes : max);
if ( min == 1 && max == 1 ) {
List<List<Integer>> combinations = new ArrayList<List<Integer>>();
computeCombinations(combinations, countGroupedNodes, 2);
IVecInt vectIntGrpXOR;
for( List<Integer> combination : combinations ) {
vectIntGrpXOR = new VecInt(2);
for( Integer index : combination ) {
vectIntGrpXOR.push(-(countFeatures-countGroupedNodes+1+index));
}
solver.addClause(vectIntGrpXOR);
//System.out.println(vectIntGrpXOR);
}
}
// Implemented on May 6, 2009
// min > 1 || max < countGroupedNodes
else if ( min > 1 || max < countGroupedNodes ) {
// step 1: from 1 to MIN-1
// step 2: from MAX+1 to countGroupedNodes
int startIndex = 0;
int endIndex = min-1;
List<List<Integer>> combinations = new ArrayList<List<Integer>>();
while( true ) {
// System.out.println("startIndex=" + startIndex +" endIndex=" + endIndex);
for( int idx = startIndex ; idx < endIndex ; idx++ ) {
computeCombinations(combinations, countGroupedNodes, idx+1);
IVecInt vectIntGrp;
// System.out.println("combination size: " + combinations.size());
for( List<Integer> combination : combinations ) {
vectIntGrp = new VecInt(countGroupedNodes);
// add negated variables
// System.out.print(">> ");
for( Integer index : combination ) {
// System.out.print(-(countFeatures-countGroupedNodes+1+index) + ",");
vectIntGrp.push(-(countFeatures-countGroupedNodes+1+index));
}
// add positive variables
for( int posIndex = 0 ; posIndex < countGroupedNodes ; posIndex++ ) {
if (!combination.contains(posIndex)) {
// System.out.print(countFeatures-countGroupedNodes+1+posIndex + ",");
vectIntGrp.push(countFeatures-countGroupedNodes+1+posIndex);
}
}
// System.out.println("");
solver.addClause(vectIntGrp);
//System.out.println(vectIntGrpXOR);
}
}
if ( endIndex == countGroupedNodes ) break;
startIndex = max;
endIndex = countGroupedNodes;
}
}
// else (Inclusive-OR grouped) <- addressed by default
}
else {
//System.out.println("Error: Other type of node!");
}
}
}
}
}
updateVariableMappings();
// add unit clauses for instantiated variables
for( FeatureTreeNode node : featureModel.getInstantiatedNodes() ) {
// System.out.println(node + ":" + node.getValue());
if ( !(node instanceof FeatureGroup)) {
IVecInt vInt = new VecInt(1);
int index = getVariableIndex(node.getID());
vInt.push(node.getValue()==1 ? index : -index);
solver.addClause(vInt);
// System.out.println("Adding unit clause for " + node.getID());
}
}
}
public static List<List<Integer>> computeCombinations(List<List<Integer>> combinations, int n, int p) {
combinations.clear();
groupCombination(combinations, null, 0, 0, n, p);
return combinations;
}
public static void groupCombination(List<List<Integer>> combinations, ArrayList<Integer> index, int level, int start, int n, int p) {
if ( level == 0 && index == null ) {
index = new ArrayList<Integer>(p);
}
if ( level == p ) {
List<Integer> theIndex = new ArrayList<Integer>();
theIndex.addAll(index);
combinations.add(theIndex);
}
else {
// if ( (n-start+3) >= p ) {
for( int i = start ; i < n ; i++ ) {
index.add(level, i);
groupCombination(combinations, index, level+1, i+1, n, p);
index.remove(level);
}
// }
}
}
/*
* Parameter 1: testValues
* -----------
* testValues: indicates which truth values should be tested for variables: 1=true, 0=false
* For dead features: testValues={1}, for common features: testValues={0}, for valid domains: testValues={1,0}
* Eg. Tests with {1} find all features that can be true, hence, the dead features are those that CANNOT be true
*
* Parameter 2: optimizations: indicate which optimizations should be used in the algorithm
* -----------
* 1) Update all variable domains whenever a variable is inspected
* 2) Add unit clause to formula for unsatisfiability cases
* 3) If feature is dead its descendants are dead (for future: if f can only be true (common), all ancestors are true)
* 4) Indicates the "SAT variable order" as well as the value order for each variable
* - move tested/completed variables to the end of the order
* - priority to non-tested values for each variable
* Dependencies:
* - optimization 5 only applies when optimization 1 is ON
* - optimization 4 improves quality of optimization 3: (parent processed prior to children improves discovery of dead features)
*
* Parameter 3: statistics map
* -----------
*/
/*
* Defaults:
* - DFS for ordering processing and SAT variables
* Variable order for processing variables (we call it "algorithm variable order" not to confuse with "SAT variable order")
* - parent features prior to their children: good for unsat cases -> improves optimization 3
*/
public byte[][] computeValidDomains(int testValues[], boolean optimizations[], Map<String,String> stats) {
// Dimension 1: variable index
// Dimension 2: variable domain (0: false, 1: true)
byte domainTable[][] = null;
// check if problem is SAT, otherwise: ERROR!
// if( !solver.isSatisfiable() ) {
// return null;
// }
try {
// creates a table to record the possible domains for each variable in the model
domainTable = new byte[featureModel.countFeatures()][2];
for( int i = 0 ; i < domainTable.length ; i++ ) {
FeatureTreeNode node = featureModel.getNodeByID(getVariableName(i+1));
if ( node.isInstantiated() ) {
domainTable[i][node.getValue()] = YES;
domainTable[i][1-node.getValue()] = NO;
}
else {
domainTable[i][0] = UNKNOWN;
domainTable[i][1] = UNKNOWN;
}
}
int satChecks = 0;
long processingTime = System.currentTimeMillis();
int varOrderIndex = 0;
// List<IConstr> state = saveSolverState();
// for each uninstantiated variable in the feature model (in a particular order)
List<FeatureTreeNode> processingVarOrder = getVariableProcessingOrder(domainTable);
// System.out.print("Processing order: ");
// for(FeatureTreeNode n : processingVarOrder) {
// System.out.print(n.getID()+",");
// }
// System.out.println();
int opt3NumVariablesEliminated = 0;
for( FeatureTreeNode uninstantiatedNode : processingVarOrder ) {
//**
// dumpDomainTable(domainTable, false);
// System.out.println("\r\n" + (varOrderIndex+1) +". Checking: " + uninstantiatedNode.getID() + "(" + getVariableIndex(uninstantiatedNode.getID()) + ")");
// init(); // create SAT solver and add clauses from FM
Solver solver = (Solver)getSolver();
// solver.setDBSimplificationAllowed(true);
int varIndex = getVariableIndex(uninstantiatedNode.getID());
// what boolean value order should be used for the test variable?
for( Integer value : getValueProcessingOrder(domainTable, varIndex, testValues) ) {
// optimization 4
// set SAT value order strategy based on current state of domain table
setVariableAndValueOrderForSAT(domainTable, varIndex, testValues, optimizations);
// System.out.println(" >> " + value);
try {
solver.assume(value==1 ? LiteralsUtils.posLit(varIndex) : LiteralsUtils.negLit(varIndex));
// solver.propagate();
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
satChecks++;
boolean isSAT = solver.isSatisfiable();
int[] solution = null;
if ( isSAT ) {
solution = satSolver.model();
}
if ( isSAT &&
((solution[getVariableIndex(uninstantiatedNode.getID())-1]<0 && value==0) ||
(solution[getVariableIndex(uninstantiatedNode.getID())-1]>0 && value==1)) ) {
domainTable[varIndex-1][value] = YES;
// optimization 1
if ( optimizations[0] ) {
// //**
// System.out.print(" ");
// for( int v : solution ) {
// System.out.print(v + ",");
// }
// System.out.println();
// //**
for( int j = varOrderIndex+1 ; j < processingVarOrder.size() ; j++ ) {
int tempIndex = getVariableIndex(processingVarOrder.get(j).getID());
//**
// System.out.println(" " + processingVarOrder.get(j).getID() + "(" + (tempIndex) +") = " + solution[tempIndex-1]);
domainTable[tempIndex-1][solution[tempIndex-1]<0?0:1] = YES;
}
}
}
else {
domainTable[varIndex-1][value] = NO;
domainTable[varIndex-1][1-value] = YES;
// optimization 2
if (optimizations[1]) {
VecInt tempVecInt = new VecInt(1);
tempVecInt.push((value==0)?varIndex:-varIndex);
satSolver.addClause(tempVecInt);
// //**
// System.out.println("UNSAT: assigning value to node " + uninstantiatedNode.getID());
}
// optimization 3
if ( optimizations[2] ) {
Collection<FeatureTreeNode> propNodes = new LinkedList<FeatureTreeNode>();
if ( value == 1 ) {
featureModel.getSubtreeNodes(featureModel.getNodeByID(uninstantiatedNode.getID()), (List)propNodes);
}
// else {
// propNodes = featureModel.ancestors(featureModel.getNodeByID(uninstantiatedNode.getID()));
// }
// System.out.println("Dead feature: " + uninstantiatedNode.getID());
for( FeatureTreeNode propNode : propNodes) {
int propVarIndex = getVariableIndex(propNode.getID());
if ( domainTable[propVarIndex-1][value] == UNKNOWN ) {
// System.out.println(" opt3 dead feature: " + propNode.getID());
domainTable[propVarIndex-1][value] = NO;
domainTable[propVarIndex-1][1-value] = YES;
VecInt tempVecInt = new VecInt(1);
tempVecInt.push((value==0)?propVarIndex:-propVarIndex);
satSolver.addClause(tempVecInt);
opt3NumVariablesEliminated++;
}
// System.out.println("propagating");
}
}
break; // do not inspect the other boolean value on UNSAT cases
}
}
varOrderIndex++;
}
processingTime = (System.currentTimeMillis() - processingTime);
stats.put("sat-checks", String.valueOf(satChecks));
stats.put("processing-time", String.valueOf(processingTime));
stats.put("opt3-eliminated-vars", String.valueOf(opt3NumVariablesEliminated));
}
catch (Exception e) {
e.printStackTrace();
}
return domainTable;
}
// protected List<IConstr> saveSolverState() {
// List<IConstr> constraints = new LinkedList<IConstr>();
// Solver solver = (Solver)getSolver();
// for( int i = 0 ; i < solver.nConstraints() ; i++ ) {
// constraints.add(solver.getDSFactory().);
// }
// return constraints;
//
// }
//
// protected void restoreSolverState(List<IConstr> constraints) {
// Solver solver = (Solver)getSolver();
// for( IConstr constraint : constraints ) {
// solver.addClause(constraint.);
//
// }
// }
public void dumpDomainTable(byte [][]table, boolean isFinal) {
int i = 0;
System.out.println("-----------------------");
for( byte[] entry : table ) {
int j = 0;
if ( isFinal ) {
if ( entry[0] == YES && entry[1] == YES ) {
System.out.println(" FREE : " + getVariableName(i+1) + ": {true, false}");
}
else if ( entry[1] == NO ) {
System.out.println(">>>>> DEAD : " + getVariableName(i+1) + ": {false}");
}
else if ( entry[0] == NO ) {
System.out.println(">>>>> COMMON: " + getVariableName(i+1) + ": {true}");
}
else {
System.out.println(" PARTIAL : " + getVariableName(i+1) + ": {" + (entry[0]==YES?"false, ":(entry[0]==NO?"":"?, ")) + (entry[1]==YES?"true":(entry[0]==NO?"":"?"))+ "}");
}
}
else {
System.out.print(" " + getVariableName(i+1) + ": {");
for( byte value : entry ) {
if ( value == YES ) {
System.out.print((j==1)+ ",");
}
else if ( value == UNKNOWN ){
System.out.print("?,");
}
j++;
}
System.out.print("}\r\n");
}
i++;
}
System.out.println("-----------------------");
}
protected List<FeatureTreeNode> getVariableProcessingOrder(final byte [][] domainTable) {
// DFS is the default variable ordering strategy
FTTraversalNodeSelector selector = new FTTraversalNodeSelector() {
public boolean select(FeatureTreeNode node) {
if ( node instanceof FeatureGroup )
return false;
int varIndex = getVariableIndex(node.getID())-1;
return (domainTable[varIndex][0]) == UNKNOWN && (domainTable[varIndex][1] == UNKNOWN);
}
};
List<FeatureTreeNode> order = FTTraversals.dfs(featureModel.getRoot(), selector);
// if (!optimization4) {
// Collections.shuffle(order);
// }
return order;
//
// List<FeatureTreeNode> list = new ArrayList<FeatureTreeNode>();
// list.addAll(featureModel.getUninstantiatedNodes());
////** Collections.shuffle(list);
}
protected List<Integer> getValueProcessingOrder(byte [][] domainTable, int varIndex, int testValues[]) {
List<Integer> order = new ArrayList<Integer>();
for( int value : testValues ) {
// if value on testValues has not been tested yet, add it to the list
if ( domainTable[varIndex-1][value] == UNKNOWN ) {
order.add(value);
}
}
return order;
}
// optimization 4
// This is the order the SAT solver will use for variables and values for constructing the search tree
private String satVarOrder[] = null;
private StaticVariableOrderSAT satOrderObj = null;
protected void setVariableAndValueOrderForSAT(byte [][] domainTable, int varIndex, int testValues[], boolean optimizations[]) {
// optimization 4: value order
// if testValues={1} -> test dead features -> 1
// if testValues={0} -> test common features -> 0
// if testValues={1,0} or {0,1} -> priority to values not tested yet
if ( optimizations[3] ) {
// DFS of the FT - FUTURE: move processed variables to the end of the order
if ( satVarOrder == null ) {
satVarOrder = new String[domainTable.length];//featureModel.countFeatures()];
FTTraversalNodeSelector selector = new FTTraversalNodeSelector() {
public boolean select(FeatureTreeNode node) {
if ( node instanceof FeatureGroup )
return false;
return true;
}
};
int index=0;
for( FeatureTreeNode node : FTTraversals.dfs(featureModel.getRoot(), selector)) {
satVarOrder[index++] = node.getID();
}
satOrderObj = new StaticVariableOrderSAT(satVarOrder, false, varName2IndexMap, varIndex2NameMap);
}
int valueOrder[] = new int[satVarOrder.length];
// System.out.print(" SAT Variable/Value Order: ");
for( int i = 0 ; i < valueOrder.length ; i++ ) {
valueOrder[i] = testValues.length==1 ? testValues[0] : (domainTable[getVariableIndex(satVarOrder[i])-1][0] == UNKNOWN ? 0 : 1);
// System.out.print(varOrder[i] + ": {" + (valueOrder[i]==1) +"} ,");
}
// System.out.println("");
satOrderObj.setValueOrder(valueOrder);
setVariableOrderObject(satOrderObj);
}
}
}