/*
* Marcilio Mendonca, January 2009
*
* Program originally create to test the "valid domains algorithm" (operation computeValidDomains())
* that computes valid domains and detect dead and common features on feature models
* using a SAT solver (SAT4J)
*
* Results to be submitted to academic conferences: SPLC'09 and GPCE'09
*
*/
package splar.apps.experiments;
import java.io.FileWriter;
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import splar.core.constraints.BooleanVariableInterface;
import splar.core.constraints.CNFClause;
import splar.core.constraints.CNFGenerator;
import splar.core.constraints.PropositionalFormula;
import splar.core.fm.FeatureGroup;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureModelException;
import splar.core.fm.FeatureModelStatistics;
import splar.core.fm.FeatureTreeNode;
import splar.core.fm.XMLFeatureModel;
import splar.core.fm.randomization.RandomFeatureModel2;
import splar.core.fm.reasoning.FMReasoningInterface;
import splar.plugins.reasoners.sat.sat4j.FMReasoningWithSAT;
// fm size, fm ecr, fm num clauses, clause density, variable order, value order,
// parent-child, num checks, dead check time, sat check time, num dead features
public class TestValidDomains {
public static void main(String args[]) {
TestValidDomains o = new TestValidDomains();
o.checkValidDomains();
}
public void checkValidDomains() {
FeatureModel fm = null;
boolean generateModels = true; // <=============
boolean saveLogs = false;
boolean checkValidDomain = false;
boolean satisfiabilityState = false; // <==== true: must be satisfiable, false: must be UNsatisfiable
int fmSize = 1000;
float ecr = 0.2f;
float density = 1f;
int numberModels = 10;
int firstModel = 1; // <========
/*
* 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)
*
* 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
*
* Opt. 1,2 -> Proposed by Mikolas Janota (paper: "Do SAT solvers make good configurators", ASPL'08)
* Opt. 3-5 -> Proposed by Marcilio Mendonca
*/
// boolean optimizations[] = new boolean[] {true,true,true,true}; // MIKOLAS + MARCILIO OPTIMIZATIONS 1-5
// boolean optimizations[] = new boolean[] {true,true,false,false}; // MIKOLAS OPTIMIZATIONS 1-2
boolean optimizations[] = new boolean[] {true,true,false,false}; // NO OPTIMIZATION
int checkType[] = {0,1}; // {0}-common features, {1}- dead features, {0,1}-valid domains
// String logPath = "c:\\users\\marcilio\\eclipse\\4watreason\\experiments\\valid-domains\\analysis\\valid-domains-logs.txt";
// String modelsPath = "c:\\users\\marcilio\\eclipse\\4watreason\\experiments\\valid-domains\\models\\";
String logPath = "c:\\users\\marcilio\\eclipse\\SPLOT\\webcontent\\genmodels\\temp\\valid-domains-logs.txt";
String modelsPath = "c:\\users\\marcilio\\eclipse\\SPLOT\\webcontent\\genmodels\\";
FMReasoningWithSAT ri = null;
for( int modelIndex = firstModel ; modelIndex <= numberModels ; modelIndex++ ) {
String fmName = "unsat-3-sat-fm"+ "-" + fmSize + "-" + (int)(ecr*100) + "-" + modelIndex;
String fileName = fmName + ".xml";
if ( generateModels) {
try {
do {
System.out.println("Trying to generate a " + (satisfiabilityState ? "SATISFIABLE" : "UNSATISFIABLE") + " model ...");
fm = gen3SATFeatureModel(fmSize, ecr, density);
ri = new FMReasoningWithSAT("MiniSAT", fm, 60000);
ri.init();
} while ( ri.isConsistent() != satisfiabilityState );
}
catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
else {
fm = new XMLFeatureModel(modelsPath + fileName, XMLFeatureModel.USE_VARIABLE_NAME_AS_ID);
try {
fm.loadModel();
} catch (FeatureModelException e) {
e.printStackTrace();
}
}
fm.setName(fmName);
FeatureModelStatistics stats = new FeatureModelStatistics(fm);
stats.update();
if ( checkValidDomain ) {
System.out.println("********** Processing Model " + modelIndex + " **********");
try {
VDData data = new VDData();
ri = new FMReasoningWithSAT("MiniSAT", fm, 60000);
ri.init();
Map<String,String> riStats = new HashMap<String,String>();
data.optimizations = optimizations;
data.checkType = checkType.length == 2 ? "Domain" : (checkType[0]==1 ? "Dead" : "Common") ;
byte [][] domainTable = ri.computeValidDomains(checkType, data.optimizations, riStats);
data.numSATChecks = riStats.get("sat-checks");
data.processingTime = riStats.get("processing-time");
data.opt3ElimVars = riStats.get("opt3-eliminated-vars");
int index = 0;
data.numCommon = 0;
data.numDead = 0;
for( byte[] entry : domainTable ) {
if ( entry[0] == FMReasoningInterface.NO ) {
// System.out.println("COMMON ===> " + ri.getVariableName(index+1));
data.numCommon++;
}
else if ( entry[1] == FMReasoningInterface.NO ) {
// System.out.println("DEAD ===> " + ri.getVariableName(index+1));
data.numDead++;
}
index++;
}
data.fmSize = fm.countFeatures();
data.modelName = fm.getName();
data.ecr = ecr;
data.clauseDensity = stats.getECClauseDensity();
data.numClauses = fm.countConstraints();
System.out.println("#SAT checks: " + data.numSATChecks);
System.out.println("Time........: " + data.processingTime);
System.out.println("#Dead/Common..: " + data.numDead + "/" + data.numCommon +"\r\n");
if ( saveLogs ) {
saveData(logPath, data);
}
}
catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
if ( generateModels ) {
saveFeatureModel(fm, stats, modelsPath + fileName);
}
}
}
public FeatureModel gen3SATFeatureModel(int fmSize, float ecr, float density) {
FeatureModel fm = new RandomFeatureModel2("fm", fmSize, 25, 25, 25, 25, 1, 6, 6, 0);
try {
fm.loadModel();
} catch (FeatureModelException e) {
e.printStackTrace();
}
int ecVar = (int)(fmSize * ecr);
// Creates a CNF formula based on ecVar distinct random feature model variables
List<BooleanVariableInterface> ecVars = new LinkedList<BooleanVariableInterface>();
for( FeatureTreeNode var : fm.getNodes() ) {
if ( !(var instanceof FeatureGroup ) && !fm.isRoot(var) ){
ecVars.add(var);
}
}
Collections.shuffle(ecVars);
int size = ecVars.size()-ecVar;
for( int i = 1 ; i <= size ; i++ ) {
ecVars.remove(ecVars.size()-1);
}
CNFGenerator cnfGenerator = new CNFGenerator();
List<CNFClause> clauses = cnfGenerator.generateCNFInstance(ecVars, null, density, 3);
// Add clauses to feature model
int clauseCounter = 1;
for( CNFClause clause : clauses ) {
try {
fm.addConstraint(new PropositionalFormula("ec_"+(clauseCounter++),clause.toPropositionalFormula()));
} catch (Exception e) {
e.printStackTrace();
}
}
return fm;
}
boolean saveHeader = true;
public void saveData(String fileName, VDData data) {
try {
FileWriter writer = new FileWriter(fileName, true);
if ( saveHeader ) {
writer.write(data.header() + "\r\n");
}
writer.write(data.toString() + "\r\n");
writer.close();
} catch (Exception e) {
// TODO: handle exception
}
saveHeader = false;
}
private void saveFeatureModel(FeatureModel fm, FeatureModelStatistics stats, String location) {
PrintStream stream = null;
PrintStream standartOut = System.out;
try {
stream = new PrintStream(location);
System.setOut(stream);
fm.dumpXML();
System.out.println("<!--");
stats.dump();
System.out.println("-->");
System.setOut(standartOut);
stream.flush();
stream.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
}
//fm size, fm ecr, fm num clauses, clause density, variable order, value order,
//parent-child, num checks, dead check time, sat check time, num dead features
class VDData {
public int fmSize;
public String modelName;
public float ecr;
public int numClauses;
public double clauseDensity;
public boolean optimizations[];
public boolean optValueOrder;
public boolean optParentChildDead;
public String numSATChecks;
public String processingTime;
public String checkType;
public int numDead;
public int numCommon;
public String opt3ElimVars;
public VDData() {
fmSize = 0;
modelName = "";
ecr = 0;
numClauses = 0;
clauseDensity = 0;
optimizations = new boolean[4];
optValueOrder = false;
optParentChildDead = false;
numSATChecks = "";
processingTime = "";
numDead = 0;
numCommon = 0;
opt3ElimVars = "";
}
public String header() {
return "check,model,#fm,ecr,#clauses,cl density,opt1,opt2,opt3,opt4,#checks,time,#dead,#common,#opt3elimvars";
}
public String toString() {
String toString = "";
toString +=
checkType + "," +
modelName + "," +
fmSize + "," +
ecr + "," +
numClauses + "," +
clauseDensity + "," +
optimizations[0] + "," +
optimizations[1] + "," +
optimizations[2] + "," +
optimizations[3] + "," +
numSATChecks + "," +
processingTime + "," +
numDead + "," +
numCommon + "," +
opt3ElimVars;
return toString;
}
}