package splar.plugins.reasoners.bdd.javabdd; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import net.sf.javabdd.BDD; import net.sf.javabdd.BDDFactory; import net.sf.javabdd.BDDFactory.ReorderMethod; import splar.core.constraints.CNFFormula; import splar.core.fm.FeatureGroup; import splar.core.fm.FeatureModel; import splar.core.fm.FeatureTreeNode; import splar.core.fm.GroupedFeature; import splar.core.fm.SolitaireFeature; import splar.core.fm.reasoning.FMReasoningException; import splar.core.heuristics.VariableOrderingHeuristic; public class FTReasoningWithBDD extends ReasoningWithBDD { public final static String BEST_VARIABLE_ORDER = "Best-Variable-Order"; // Best Variable Order - very costly public final static String WORST_VARIABLE_ORDER = "Worst-Variable-Order"; // Worst Variable Order - very costly protected FeatureModel featureModel; private BDDGenerationStatistics bddStats; private String searchType; public FTReasoningWithBDD(FeatureModel featureModel, VariableOrderingHeuristic voHeuristic, int nodeNum, int cacheSize, long maxBuildingtime, String orderingFormulasStrategy) { this(featureModel, voHeuristic, nodeNum, cacheSize, maxBuildingtime, BDDFactory.REORDER_NONE, orderingFormulasStrategy); } public FTReasoningWithBDD(FeatureModel featureModel, VariableOrderingHeuristic voHeuristic, int nodeNum, int cacheSize, long maxBuildingtime, ReorderMethod reorderMethod, String orderingFormulasStrategy) { super(voHeuristic, nodeNum, cacheSize, maxBuildingtime, reorderMethod, orderingFormulasStrategy); this.featureModel = featureModel; this.searchType = null; bddFactory.setVarNum(getFeatureModel().countNodes()); } public FTReasoningWithBDD(FeatureModel featureModel, int nodeNum, int cacheSize, String searchType, long maxBuildingtime, String orderingFormulasStrategy) { this(featureModel, nodeNum, cacheSize, searchType, maxBuildingtime, BDDFactory.REORDER_NONE, orderingFormulasStrategy); } public FTReasoningWithBDD(FeatureModel featureModel, int nodeNum, int cacheSize, String searchType, long maxBuildingtime, ReorderMethod reorderMethod, String orderingFormulasStrategy) { super(null, nodeNum, cacheSize, maxBuildingtime, reorderMethod, orderingFormulasStrategy); this.featureModel = featureModel; this.searchType = searchType; } public void init() throws Exception { super.init(); long start = System.currentTimeMillis(); setInitialBDDState(start - ((long)getHeuristicRunningTime()+bddBuildingTime)); bddBuildingTime += (System.currentTimeMillis() - start); } protected void setInitialBDDState(long start) throws Exception { variables = initVars(theOriginalBDD.getFactory().varNum()); } public void saveState(String stateID) { super.saveState(stateID); featureModel.saveState(stateID); } public void restoreState(String stateID) { super.restoreState(stateID); featureModel.restoreState(stateID); } public void discardState(String stateID) { super.discardState(stateID); featureModel.discardState(stateID); } protected BDD createBDD(BDDFactory bddFactory, String orderingFormulasStrategy) throws Exception { if ( reorderMethod != BDDFactory.REORDER_NONE ) { super.initBDDReorder(getFeatureModel().countNodes()); } BDD bdd = null; long start = System.currentTimeMillis(); // create BDD based on Statistics (eg., best order, worst order) if ( searchType != null ) { if ( searchType == BEST_VARIABLE_ORDER || searchType == WORST_VARIABLE_ORDER) { bdd = createBDDBasedOnStatistics(searchType); this.heuristicRunningTime = 0; } } // create BDD based on FEATURE TREE TRAVERSAL HEURISTICS else if ( variableOrderingHeuristic != null ){ // bddFactory.setVarNum(countFMNodes); varIndex2NameMap = variableOrderingHeuristic.run(toCNF()); varName2IndexMap = VariableOrderingHeuristic.variableOrderingAsHashMap(varIndex2NameMap); bdd = createBDDStructure(start - (long)getHeuristicRunningTime(), orderingFormulasStrategy); } // root node is always true bdd.andWith(bddFactory.ithVar(varName2IndexMap.get(featureModel.getRoot().getID()))); this.bddBuildingTime = System.currentTimeMillis() - start; // theOriginalBDD = bdd; // theWorkingBDD = theOriginalBDD; return bdd; } protected CNFFormula toCNF() { return featureModel.FT2CNF(); } protected BDD createBDDBasedOnStatistics(String searchType) throws Exception { int countFMNodes = getFeatureModel().countNodes(); varName2IndexMap = new HashMap<String,Integer>(); varIndex2NameMap = new String[countFMNodes]; generateBDDStatistics(); int searchTypeIndex = -1; if ( searchType.compareToIgnoreCase(BEST_VARIABLE_ORDER) == 0 ) { searchTypeIndex = bddStats.getBestVariableOrderIndex(); } else if ( searchType.compareToIgnoreCase(WORST_VARIABLE_ORDER) == 0 ) { searchTypeIndex = bddStats.getWorstVariableOrderIndex(); } // update the FT-BDD mapping structures String varOrder[] = bddStats.getVariableOrder(searchTypeIndex); for( int i = 0 ; i < varOrder.length ; i++ ) { varName2IndexMap.put(varOrder[i], i); varIndex2NameMap[i] = varOrder[i]; } return bddStats.getBDD(searchTypeIndex); } private void generateBDDStatistics() throws Exception { Vector<String> variables = new Vector<String>(); for( Iterator<FeatureTreeNode> it = featureModel.getNodes().iterator(); it.hasNext() ; ) { FeatureTreeNode node = it.next(); if ( !(node instanceof GroupedFeature)) { variables.add(node.getID()); } } bddStats = null; String strVariables[] = variables.toArray(new String[0]); genBDDStats(strVariables, null, 0, System.currentTimeMillis()); } private void genBDDStats(String theVariables[], int index[], int level, long start) throws Exception { if ( (System.currentTimeMillis() - start) > maxBuildingTime ) { throw new BDDExceededBuildingTimeException("FTReasoningWithBDD: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); } if ( index == null ) { index = new int[theVariables.length]; for( int i = 0 ; i < index.length ; i++ ) index[i] = -1; } if ( level == theVariables.length ) { int expandIndex = 0; for( int j = 0 ; j < index.length ; j++ ) { FeatureTreeNode node = featureModel.getNodeByID(theVariables[index[j]]); if (node instanceof FeatureGroup) { FeatureGroup group = (FeatureGroup)node; int groupChildCount = group.getChildCount(); for( int k = 0 ; k < groupChildCount; k++ ) { FeatureTreeNode childNode = (FeatureTreeNode)group.getChildAt(k); varName2IndexMap.put(childNode.getID(), expandIndex+j+k); varIndex2NameMap[expandIndex+j+k] = childNode.getID(); } expandIndex += groupChildCount-1; } else { varName2IndexMap.put(theVariables[index[j]], j+expandIndex); varIndex2NameMap[j+expandIndex] = theVariables[index[j]]; } } try { theOriginalBDD = createBDDStructure(start, orderingFormulasStrategy); theWorkingBDD = theOriginalBDD; setInitialBDDState(start); if ( bddStats == null ) { bddStats = new BDDGenerationStatistics(theVariables.length); } bddStats.addStats(bddFactory, theWorkingBDD, varIndex2NameMap); } catch( Exception e ) { e.printStackTrace(); } } else { for( int i = 0 ; i < theVariables.length ; i++ ) { if ( index[i] == -1) { index[i] = level; genBDDStats(theVariables, index, level+1, start); index[i] = -1; } } } } protected BDD createBDDStructure(long startTime, String orderingFormulasStrategy) throws BDDExceededBuildingTimeException { // System.out.println("Using strategy: " + orderingFormulasStrategy); if ( orderingFormulasStrategy.compareToIgnoreCase("level-order") == 0 ) { return levelOrderFormulaOrdering(startTime); } // if strategy is "undefined" uses preorder return preOrderFormulaOrdering(startTime); } protected BDD preOrderFormulaOrdering(long startTime) throws BDDExceededBuildingTimeException { return preOrderFormulaOrderingRec(featureModel.getRoot(), startTime); } protected BDD preOrderFormulaOrderingRec(FeatureTreeNode curNode, long startTime) throws BDDExceededBuildingTimeException { BDD bdd = bddFactory.one(); BDD parentNodeBDD = bddFactory.ithVar(varName2IndexMap.get(curNode.getID())); int count = curNode.getChildCount(); if ( count > 0 ) { BDD childBDD = null; for( int i = 0 ; i < count ; i++ ) { FeatureTreeNode childNode = ((FeatureTreeNode)curNode.getChildAt(i)); // It's a solitaire feature if ( childNode instanceof SolitaireFeature ) { SolitaireFeature solitaireNode = (SolitaireFeature)childNode; childBDD = bddFactory.ithVar(varName2IndexMap.get(childNode.getID())); // if node is optional relation is "child implies parent" if ( solitaireNode.isOptional() ) { bdd.andWith(childBDD.imp(parentNodeBDD.id())); } // if node is mandatory relation is "child iff parent" else { bdd.andWith(childBDD.biimp(parentNodeBDD.id())); } // recursive structure BDD subtreeBDD = preOrderFormulaOrderingRec(childNode, startTime); bdd.andWith(subtreeBDD); } // It's a feature group else if ( childNode instanceof FeatureGroup ) { FeatureGroup fGroup = (FeatureGroup)childNode; BDD fgBDD = createFeatureGroupBDDStructure(parentNodeBDD.id(),fGroup, null, bddFactory, startTime); bdd.andWith(fgBDD); // recursive call for( int j = 0 ; j < fGroup.getChildCount() ; j++ ) { FeatureTreeNode groupedNode = (FeatureTreeNode)fGroup.getChildAt(j); BDD subtreeBDD = preOrderFormulaOrderingRec(groupedNode, startTime); bdd.andWith(subtreeBDD); } } else { System.out.println("Error: Other type of node!"); } } } return bdd; } protected BDD levelOrderFormulaOrdering(long startTime) throws BDDExceededBuildingTimeException { BDD bdd = bddFactory.one(); Vector<FeatureTreeNode> nodes = new Vector<FeatureTreeNode>(); nodes.add(getFeatureModel().getRoot()); while ( nodes.size() > 0 ) { if ( (System.currentTimeMillis() - startTime) > maxBuildingTime ) { throw new BDDExceededBuildingTimeException("PF2BDDParser: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); } // consumes first node FeatureTreeNode curNode = nodes.firstElement(); nodes.remove(curNode); if ( curNode != null ) { BDD nodeBDD = null; nodeBDD = bddFactory.ithVar(varName2IndexMap.get(curNode.getID())); int count = curNode.getChildCount(); if ( count > 0 ) { BDD childBDD = null; for( int i = 0 ; i < count ; i++ ) { FeatureTreeNode childNode = ((FeatureTreeNode)curNode.getChildAt(i)); // It's a solitaire feature if ( childNode instanceof SolitaireFeature ) { nodes.add(childNode); SolitaireFeature solitaireNode = (SolitaireFeature)childNode; childBDD = bddFactory.ithVar(varName2IndexMap.get(childNode.getID())); // if node is optional relation is "child implies parent" if ( solitaireNode.isOptional() ) bdd.andWith(childBDD.imp(nodeBDD.id())); // if node is mandatory relation is "child iff parent" else bdd.andWith(childBDD.biimp(nodeBDD.id())); } // It's a feature group else if ( childNode instanceof FeatureGroup ) { FeatureGroup fGroup = (FeatureGroup)childNode; bdd.andWith(createFeatureGroupBDDStructure(nodeBDD.id(),fGroup, nodes, bddFactory, startTime)); } else { System.out.println("Error: Other type of node!"); } } } } } //set root node to TRUE bdd.andWith(bddFactory.ithVar(varName2IndexMap.get(getFeatureModel().getRoot().getID())).id().or(bddFactory.zero())); return bdd; } protected BDD createFeatureGroupBDDStructure(BDD parentBDD, FeatureGroup node, Vector<FeatureTreeNode> nodes, BDDFactory bddFactory, long start) throws BDDExceededBuildingTimeException { BDD bdd = null; int childCount = node.getChildCount(); if ( childCount > 0 ) { int min = node.getMin(); int max = node.getMax(); FeatureTreeNode tempNode1 = null,tempNode2 = null; // cardinality: min = 1, max = * ( X1 v X2 v .... v Xn) if ( (min == 1) && (max == -1) ) { tempNode1 = (FeatureTreeNode)node.getChildAt(0); // changed to start with first grouped child bdd = bddFactory.ithVar(varName2IndexMap.get(tempNode1.getID())); for( int i = 1 ; i < childCount ; i++ ) { if ( (System.currentTimeMillis() - start) > maxBuildingTime ) { throw new BDDExceededBuildingTimeException("PF2BDDParser: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); } tempNode2 = (FeatureTreeNode)node.getChildAt(i); // add grouped nodes to the nodes list if ( nodes != null ) { nodes.add(tempNode1); } // updates the BDD BDD bdd2 = bddFactory.ithVar(varName2IndexMap.get(tempNode2.getID())); bdd.orWith(bdd2); } bdd.biimpWith(parentBDD); if ( nodes != null ) { nodes.add(tempNode2); } } // cardinality: min = max = 1 (X1 ^ !x2 ^ ... ^ !Xn) v ( x2 ^ !x1 ^ ... ^ !xn) v ... v ( xn ^ !x1 ^ .... ^ !xn-1 ) else if ( (min == max) && (min == 1) ){ bdd = bddFactory.one(); for( int i = 0 ; i < childCount ; i++ ) { tempNode1 = (FeatureTreeNode)node.getChildAt(i); // add grouped nodes to the nodes list if ( nodes != null ) { nodes.add(tempNode1); } BDD bdd1 = bddFactory.ithVar(varName2IndexMap.get(tempNode1.getID())); BDD bdd2 = bddFactory.zero(); for( int j = 0 ; j < childCount ; j++ ) { if ( (System.currentTimeMillis() - start) > maxBuildingTime ) { throw new BDDExceededBuildingTimeException("PF2BDDParser: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); } if ( i != j ) { tempNode2 = (FeatureTreeNode)node.getChildAt(j); bdd2.orWith(bddFactory.ithVar(varName2IndexMap.get(tempNode2.getID())).id()); } } bdd.andWith(bdd2.not().and(parentBDD).biimp(bdd1)); } } } return bdd; } // protected BDD createFeatureGroupBDDStructure(BDD parentBDD, FeatureGroup node, Vector<FeatureTreeNode> nodes, BDDFactory bddFactory, long start) throws BDDExceededBuildingTimeException { // BDD bdd = null; // int childCount = node.getChildCount(); // if ( childCount > 0 ) { // int min = node.getMin(); // int max = node.getMax(); // FeatureTreeNode tempNode1 = null,tempNode2 = null; // // cardinality: min = 1, max = * ( X1 v X2 v .... v Xn) // if ( (min == 1) && (max == -1) ) { // bdd = bddFactory.zero(); // for( int i = 1 ; i < childCount ; i++ ) { // if ( (System.currentTimeMillis() - start) > maxBuildingTime ) { // throw new BDDExceededBuildingTimeException("PF2BDDParser: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); // } // tempNode1 = (FeatureTreeNode)node.getChildAt(i-1); // tempNode2 = (FeatureTreeNode)node.getChildAt(i); // // add grouped nodes to the nodes list // if ( nodes != null ) { // nodes.add(tempNode1); // } // // updates the BDD // BDD bdd1 = bddFactory.ithVar(varName2IndexMap.get(tempNode1.getID())); // BDD bdd2 = bddFactory.ithVar(varName2IndexMap.get(tempNode2.getID())); // bdd.orWith(bdd1.or(bdd2)); // } // bdd.biimpWith(parentBDD); // if ( nodes != null ) { // nodes.add(tempNode2); // } // } // // cardinality: min = max = 1 (X1 ^ !x2 ^ ... ^ !Xn) v ( x2 ^ !x1 ^ ... ^ !xn) v ... v ( xn ^ !x1 ^ .... ^ !xn-1 ) // else if ( (min == max) && (min == 1) ){ // bdd = bddFactory.one(); // for( int i = 0 ; i < childCount ; i++ ) { // tempNode1 = (FeatureTreeNode)node.getChildAt(i); // // add grouped nodes to the nodes list // if ( nodes != null ) { // nodes.add(tempNode1); // } // BDD bdd1 = bddFactory.ithVar(varName2IndexMap.get(tempNode1.getID())); // BDD bdd2 = bddFactory.zero(); // for( int j = 0 ; j < childCount ; j++ ) { // if ( (System.currentTimeMillis() - start) > maxBuildingTime ) { // throw new BDDExceededBuildingTimeException("PF2BDDParser: Maximum time allowed for BDD construction exceeded: " + maxBuildingTime + " ms", ""); // } // if ( i != j ) { // tempNode2 = (FeatureTreeNode)node.getChildAt(j); // bdd2.orWith(bddFactory.ithVar(varName2IndexMap.get(tempNode2.getID())).id()); // } // } // bdd.andWith(bdd2.not().and(parentBDD).biimp(bdd1)); // } // } // } // return bdd; // } public FeatureModel getFeatureModel() { return featureModel; } /**************************************************************************************************************** * REASONING OPERATIONS ****************************************************************************************************************/ /**************************************************************************************************************** * Compute valid domains for all features ****************************************************************************************************************/ byte[][] domainTable; int unknownDomains; boolean debug = false; @Override public Map<String,Boolean[]> allValidDomains(Map<String,String> stats) throws FMReasoningException { try { // Initializes domain table updating it with feature model currently assigned features domainTable = new byte[this.getFeatureModel().countFeatures()][2]; unknownDomains = domainTable.length * 2; for( int i = 0 ; i < domainTable.length ; i++ ) { String varName = getVariableName(i); FeatureTreeNode varNode = getFeatureModel().getNodeByID(varName); if ( varNode != null && varNode.isInstantiated() ) { domainTable[i][varNode.getValue()] = YES; domainTable[i][1-varNode.getValue()] = NO; unknownDomains -= 2; } else { domainTable[i][0] = UNKNOWN; domainTable[i][1] = UNKNOWN; } } if (debug) debugDomainTable(false); BDDTraversalNodeDFS traversal = new BDDTraversalNodeDFS() { int lastLevelChecked = 0; int visitedNodes = 0; public boolean searchStopped() { return unknownDomains == 0; } private String varLabel(BDD bddNode) { if ( bddNode.isOne() ) { return "1T"; } if ( bddNode.isZero() ) { return "0T"; } return ""+bddNode.var(); } private void updateDomainTable(BDDFactory factory, int startLevel, int endLevel) { for( int i = startLevel ; i > endLevel ; i-- ) { if (debug) System.out.println(" updating [level=" + i + ",var=" + factory.level2Var(i)+ "] name=" + getVariableName(factory.level2Var(i))); int varIndex = factory.level2Var(i); if ( domainTable[varIndex][0] == UNKNOWN ) { domainTable[varIndex][0] = YES; unknownDomains--; } if ( domainTable[varIndex][1] == UNKNOWN ) { domainTable[varIndex][1] = YES; unknownDomains--; } } } public void onOneTerminalFound(Set<String> path, byte solution[]) { updateDomainTable(getBDDFactory(), domainTable.length-1, lastLevelChecked); super.onOneTerminalFound(path, solution); } public void onVisitingNode(BDD bddNode, Set<String> path, byte solution[]) { if ( !bddNode.isZero() && !bddNode.isOne() ) { BDDFactory factory = bddNode.getFactory(); if (debug) System.out.println(++visitedNodes + ": visiting- [level=" + factory.var2Level(bddNode.var())+ ",var=" + bddNode.var()+ "] " + getVariableName(bddNode.var()) + "(" + varLabel(bddNode.low()) + "," + varLabel(bddNode.high()) + ")"); if (debug) System.out.println(" last visited level: " + lastLevelChecked); int currentVar = bddNode.var(); updateDomainTable(bddNode.getFactory(), factory.var2Level(currentVar)-1, lastLevelChecked); BDD low = bddNode.low(); BDD high = bddNode.high(); if ( !low.isZero() && domainTable[currentVar][0] == UNKNOWN ) { domainTable[currentVar][0] = YES; unknownDomains--; } if ( !high.isZero() && domainTable[currentVar][1] == UNKNOWN) { domainTable[currentVar][1] = YES; unknownDomains--; } low.free(); high.free(); if ( debug) debugDomainTable(false); } } public boolean canVisitNode(BDD bddNode, boolean polarity) { lastLevelChecked = getBDDFactory().var2Level(bddNode.var()); if (!super.canVisitNode(bddNode, polarity)) { BDD nextNode = polarity ? bddNode.high() : bddNode.low(); updateDomainTable(getBDDFactory(), getBDDFactory().var2Level(nextNode.var())-1, lastLevelChecked); nextNode.free(); return false; } return true; } public void onSkippedNode(BDD bddNode, boolean polarity, Set<String> path, byte[] bddPath) { } }; traversal.dfs(getBDD().id()); // all UNKNOWN domain table entries are set to NO for( int i = 0 ; i < domainTable.length ; i++ ) { if ( domainTable[i][0] == UNKNOWN ) domainTable[i][0] = NO; if ( domainTable[i][1] == UNKNOWN ) domainTable[i][1] = NO; } Map<String,Boolean[]> allDomains = new HashMap<String, Boolean[]>(); for( int i = 0 ; i < domainTable.length ; i++ ) { List<Boolean> domain = new LinkedList<Boolean>(); if ( domainTable[i][0] == YES ) { domain.add(false); } if ( domainTable[i][1] == YES ) { domain.add(true); } allDomains.put(getVariableName(i),domain.toArray(new Boolean[0])); } //debugDomainTable(false); return allDomains; } catch( Exception e ) { throw new FMReasoningException(e); } } public void debugDomainTable(boolean breakLine) { System.out.println("Unknown domains: " + unknownDomains); System.out.println("Domain Table ---------------------------"); int varIndex = 0; for( byte varDomain[] : domainTable ) { System.out.print(getVariableName(varIndex++) + ":[" + (varDomain[0] == YES ? "0," : "") + (varDomain[1] == YES ? "1" : "") +"] "); // System.out.print(reasoner.getVarNameByIndex(varIndex++) + ":[" + varDomain[0] + "," + varDomain[1] +"] "); if (breakLine ) System.out.println(); } System.out.println(); } }