package splar.core.heuristics;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import splar.core.constraints.BooleanVariable;
import splar.core.constraints.BooleanVariableInterface;
import splar.core.constraints.CNFClause;
import splar.core.constraints.CNFFormula;
import splar.core.constraints.CNFLiteral;
import splar.core.constraints.PropositionalFormula;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureTreeNode;
import splar.core.util.hypergraphs.Hyperedge;
import splar.core.util.hypergraphs.Hypergraph;
import splar.core.util.hypergraphs.Vertex;
public class FTPreOrderSortedECTraversalHeuristic extends FTPreOrderTraversalHeuristic {
public static final int SIZE_SORT = 10;
public static final int FORCE_SORT = 20;
private int sortType = SIZE_SORT;
public FTPreOrderSortedECTraversalHeuristic(String name, FeatureModel featureModel, int sortType) {
super(name, featureModel);
this.sortType = sortType;
}
protected FeatureTreeNode[] orderChildNodes(FeatureTreeNode node) {
List<FeatureTreeNode> orderedNodes = new ArrayList<FeatureTreeNode>();
List<CNFClause> clauses = (List<CNFClause>)node.getAttachedData();
// Step 1: Creates clusters based on their clause dependencies
// Step 2: Sorts clusters (ascending-order) based on the size of the subtrees rooted by their nodes, say:
NodeClusterManager clusterManager = new NodeClusterManager(featureModel);
NodeCluster clusters[] = clusterManager.processCluster(node, clauses);
node.setProperty("clusters", clusters);
// Step 3: Sort cluster nodes based on their size; single-node clusters are not considered for obvious reasons, say:
for( NodeCluster cluster : clusters ) {
FeatureTreeNode nodes[] = null;
if ( sortType == SIZE_SORT || cluster.countNodes() <= 2) {
nodes = cluster.sortedNodes();
}
else if ( sortType == FORCE_SORT ){
nodes = cluster.sortedNodesbyFORCE(clauses);
}
for( FeatureTreeNode sortedNode : nodes ) {
orderedNodes.add(sortedNode);
}
}
// Step 4: Return the nodes order
return orderedNodes.toArray(new FeatureTreeNode[0]);
}
protected void runPreProcessing(CNFFormula cnf) {
featureModel.resetNodesAttachedData();
// attach to some nodes their correspondent CNF clauses
for( PropositionalFormula pf : featureModel.getConstraints() ) {
for( CNFClause cnfClause : pf.toCNFClauses() ) {
CNFClause createdClause = new CNFClause();
FeatureTreeNode node = createClause(cnfClause, createdClause);
if ( createdClause.countVariables() > 1 ) {
List<CNFClause> nodeAttachedList = (List<CNFClause>)node.getAttachedData();
if ( nodeAttachedList == null ) {
nodeAttachedList = new Vector<CNFClause>();
node.attachData(nodeAttachedList);
}
nodeAttachedList.add(createdClause);
}
}
}
}
protected FeatureTreeNode createClause(CNFClause clause, CNFClause returnClause) {
// for each variable in "clause" creates a list with its ancestors
List<List<FeatureTreeNode>> ancestorsList = new Vector<List<FeatureTreeNode>>();
for( BooleanVariableInterface var : clause.getVariables() ) {
List<FeatureTreeNode> ancestors = new ArrayList<FeatureTreeNode>();
FeatureTreeNode node = featureModel.getNodeByID(var.getID());
if ( node != null ) {
ancestors.add(0,node);
FeatureTreeNode ancestorNode = (FeatureTreeNode)node.getParent();
// ancestorNode = ( ancestorNode instanceof FeatureGroup )? (FeatureTreeNode)ancestorNode.getParent() : ancestorNode;
while( ancestorNode != null ) {
ancestors.add(0,ancestorNode);
ancestorNode = (FeatureTreeNode)ancestorNode.getParent();
// ancestorNode = ( ancestorNode instanceof FeatureGroup )? (FeatureTreeNode)ancestorNode.getParent() : ancestorNode;
}
ancestorsList.add(ancestors);
}
}
// find the list with the lowest number of variables
int minSize = Integer.MAX_VALUE;
for( List<FeatureTreeNode> list : ancestorsList) {
int listSize = list.size();
if ( listSize < minSize ) {
minSize = listSize;
}
}
// find the lowest-level common ancestor and its corresponding children that are
// roots on the subtrees containing the variables in 'clause'
FeatureTreeNode commonAncestor = null;
boolean finalCommonAncestorFound = false;
int index = 0;
while( index < minSize && !finalCommonAncestorFound ) {
FeatureTreeNode ancestorNode = null;
for( List<FeatureTreeNode> ancestors : ancestorsList ) {
FeatureTreeNode tempNode = ancestors.get(index);
if ( ancestorNode == null ) {
ancestorNode = tempNode;
}
else if ( tempNode != ancestorNode ) {
finalCommonAncestorFound = true;
}
}
if ( !finalCommonAncestorFound ) {
commonAncestor = ancestorNode;
}
index++;
}
Set<BooleanVariableInterface> variables = new LinkedHashSet<BooleanVariableInterface>();
for( List<FeatureTreeNode> ancestors : ancestorsList ) {
if ( ancestors.size() >= index ) {
variables.add(new BooleanVariable(ancestors.get(index-1).getID()));
}
}
if ( variables.size() > 1 ) {
for( BooleanVariableInterface var : variables ) {
returnClause.addLiteral(new CNFLiteral(var,true));
}
}
return commonAncestor;
}
}
/*
* Given a node P, its child nodes A, B, C, D, E, F, G, and clauses (A,D), (A,F), (B,G)
*
* Step 1: Creates clusters based on their clause dependencies
* ------
* 1. (A,D,F)
* 2. (B,G)
* 3. (C)
* 4. (E)
* (*) Note that variables not in the clauses form a cluster each
*
* Step 2: Sorts clusters (ascending-order) based on the size of the subtrees rooted by their nodes, say:
* ------
* 1. (B,G) - 78 nodes
* 2. (E) - 83 nodes
* 3. (C) - 187 nodes
* 4. (A,D,F) - 200 nodes
*
* Step 3: Sort cluster nodes based on their size; single-node clusters are not considered for obvious reasons, say:
* ------
* 1. (B,G) - 18 + 60 = 78 nodes
* 2. (E) - 83 nodes
* 3. (C) - 187 nodes
* 4. (D,F,A) - 20 + 80 + 100 = 200 nodes
*
* Step 4: Return the nodes order
* ------
* >> B, G, E, C, D, F, A
*/
class NodeClusterManager {
private FeatureModel featureModel;
public NodeClusterManager( FeatureModel featureModel ) {
this.featureModel = featureModel;
}
public NodeCluster[] processCluster(FeatureTreeNode node, List<CNFClause> clauses) {
List<NodeCluster> nodeCluster = new ArrayList<NodeCluster>();
// STEP 1: Creates clusters based on their clause dependencies
// *********************************************************************
// clauses mean dependency among subtrees... cluster have to be created and sorted
if ( clauses != null ) {
Hypergraph hyperGraph = new Hypergraph();
// child nodes form the hypergraph vertices, each child node is also a single hyperedge
for( int i = 0 ; i < node.getChildCount() ; i++ ) {
FeatureTreeNode childNode = (FeatureTreeNode)node.getChildAt(i);
Vertex vertex = new Vertex(childNode.getID());
hyperGraph.addVertex(vertex);
Hyperedge hyperedge = new Hyperedge();
hyperedge.addVertex(vertex);
hyperGraph.addHyperEdge(hyperedge);
}
// each clause becomes a hypergraph with a single hyperedge, ie, the clause itself
// each hypergraph is then merged into the main hypergraph
for( CNFClause clause : clauses ) {
Hypergraph clauseHyperGraph = new Hypergraph();
Hyperedge clauseHyperEdge = new Hyperedge();
for( CNFLiteral literal : clause.getLiterals() ) {
Vertex vertex= hyperGraph.getVertexByName(literal.getVariable().getID());
clauseHyperGraph.addVertex(vertex);
clauseHyperEdge.addVertex(vertex);
}
clauseHyperGraph.addHyperEdge(clauseHyperEdge);
hyperGraph.merge(clauseHyperGraph);
}
// create clusters, one for each hyperedge
for( Hyperedge hyperedge : hyperGraph.getHyperedges() ) {
NodeCluster cluster = new NodeCluster(featureModel);
for( Vertex vertex : hyperedge.getVertices() ) {
cluster.addNode(vertex.getName());
}
nodeCluster.add(cluster);
}
}
// no clauses, then it's much simpler: each node forms a single cluster
else {
for( int i = 0 ; i < node.getChildCount() ; i++ ) {
FeatureTreeNode childNode = (FeatureTreeNode)node.getChildAt(i);
NodeCluster cluster = new NodeCluster(featureModel);
cluster.addNode(childNode.getID());
nodeCluster.add(cluster);
}
}
NodeCluster sortedNodeClusters[] = new NodeCluster[nodeCluster.size()];
for( int index = 0 ; index < nodeCluster.size() ; index++ ) {
sortedNodeClusters[index] = nodeCluster.get(index);
}
// Step 2: Sorts clusters (ASCENDING-order) based on the size of the subtrees rooted by their nodes, say:
// *********************************************************************
Comparator<NodeCluster> c = new Comparator<NodeCluster>() {
public int compare(NodeCluster cluster1, NodeCluster cluster2) {
int size1 = cluster1.size();
int size2 = cluster2.size();
return ( size1 > size2 ? 1 : ((size1 < size2) ? -1 : 0) );
}
};
java.util.Arrays.sort(sortedNodeClusters, c);
return sortedNodeClusters;
}
}
class NodeCluster {
private FeatureModel featureModel;
private List<FeatureTreeNode> nodes;
private int size = -1;
private List<CNFClause> identifiedClauses;
public NodeCluster(FeatureModel featureModel) {
this.featureModel = featureModel;
this.nodes = new ArrayList<FeatureTreeNode>();
this.identifiedClauses = null;
}
public int countNodes() {
return nodes.size();
}
public List<CNFClause> identifyClauses(List<CNFClause> clauses) {
if ( identifiedClauses != null ) {
return identifiedClauses;
}
identifiedClauses = new ArrayList<CNFClause>();
// for each clause
for( CNFClause clause : clauses) {
boolean clauseFound = false;
// for each cluster node
for( Iterator<FeatureTreeNode> it1 = nodes.iterator() ; it1.hasNext() && !clauseFound ; ) {
FeatureTreeNode clusterNode = it1.next();
// for each clause variable
for ( Iterator<BooleanVariableInterface> it2 = clause.getVariables().iterator() ; it2.hasNext() && !clauseFound; ) {
BooleanVariableInterface clauseVar = it2.next();
// check if clause var is part of cluster node set
if ( clauseVar.getID().equals(clusterNode.getID())) {
identifiedClauses.add(clause);
clauseFound = true;
}
}
}
}
return identifiedClauses;
}
public void addNode(String nodeID) {
nodes.add(featureModel.getNodeByID(nodeID));
}
// count number of nodes for each cluster-node subtree
public int size() {
if ( size == -1 ) {
for( FeatureTreeNode node : nodes ) {
size += featureModel.countNodes(node);
}
}
return size;
}
// cluster nodes are sorted in ASCENDING order of the size of their subtrees
public FeatureTreeNode[] sortedNodes() {
FeatureTreeNode[] sortedNodes = new FeatureTreeNode[nodes.size()];
int index = 0;
for( FeatureTreeNode node : nodes ) {
sortedNodes[index++] = node;
}
if ( sortedNodes.length > 1 ) {
Comparator<FeatureTreeNode> c = new Comparator<FeatureTreeNode>() {
public int compare(FeatureTreeNode node1, FeatureTreeNode node2) {
int size1 = featureModel.countNodes(node1);
int size2 = featureModel.countNodes(node2);
return ( size1 > size2 ? 1 : ((size1 < size2) ? -1 : 0) );
}
};
java.util.Arrays.sort(sortedNodes, c);
}
return sortedNodes;
}
// sort nodes based on FORCE heuristic
public FeatureTreeNode[] sortedNodesbyFORCE(List<CNFClause> allClauses) {
FORCEVariableOrderingHeuristic force = new FORCEVariableOrderingHeuristic("",1);
CNFFormula cnf = new CNFFormula();
identifiedClauses = identifyClauses(allClauses);
cnf.addClauses(identifiedClauses);
String vo[] = force.run(cnf);
FeatureTreeNode voNodes[] = new FeatureTreeNode[vo.length];
int index = 0;
for( String nodeID : vo ) {
voNodes[index++] = featureModel.getNodeByID(nodeID);
}
return voNodes;
}
}