/** * */ package edu.isi.karma.modeling.research.graphmatching.util; import java.util.Iterator; import java.util.LinkedList; /** * @author riesen * */ public class TreeNode implements Comparable<TreeNode> { /** nodes of g1 are mapped to...*/ private int[] matching; /** nodes of g2 are mapped to...*/ private int[] inverseMatching; /** the current cost of this partial solution*/ private double cost; /** the cost function defines the cost of individual node/edge operations*/ private CostFunction cf; /** the adjacency-matrix of the graphs */ private Edge[][] a1; private Edge[][] a2; /** the original graphs */ private Graph originalGraph1; private Graph originalGraph2; /** the graphs where the processed nodes are removed */ private Graph unusedNodes1; private Graph unusedNodes2; /** weighting factor for edge operations * = 0.5 if undirected edges are used (1.0 otherwise) * */ private double factor; /** * constructor for the initial empty solution * @param g2 * @param g1 * @param cf * @param factor */ public TreeNode(Graph g1, Graph g2, CostFunction cf, double factor) { this.unusedNodes1 = g1; this.unusedNodes2 = g2; this.originalGraph1 = g1; this.originalGraph2 = g2; this.a1 = g1.getAdjacenyMatrix(); this.a2 = g2.getAdjacenyMatrix(); this.cost = 0; this.cf = cf; this.matching = new int[g1.size()]; this.inverseMatching = new int[g2.size()]; for (int i = 0; i < this.matching.length; i++) { this.matching[i] = -1; } for (int i = 0; i < this.inverseMatching.length; i++) { this.inverseMatching[i] = -1; } this.factor = factor; } /** * copy constructor in order to generate successors * of treenode @param o */ public TreeNode(TreeNode o) { this.unusedNodes1 = (Graph) o.getUnusedNodes1().clone(); this.unusedNodes2 = (Graph) o.getUnusedNodes2().clone(); this.cost = o.getCost(); this.cf = o.getCf(); this.matching =o.matching.clone(); this.inverseMatching = o.inverseMatching.clone(); this.a1 = o.a1; this.a2 = o.a2; this.originalGraph1 = o.originalGraph1; this.originalGraph2 = o.originalGraph2; this.factor = o.factor; } /** * @return a list of successsors of this treenode (extended solutions to * *this* solution) * @param factor = 0.5 if edges are undirected (since there are edges are in both directions) */ public LinkedList<TreeNode> generateSuccessors() { // list with successors LinkedList<TreeNode> successors = new LinkedList<TreeNode>(); // all nodes of g2 are processed, the remaining nodes of g1 are deleted if (this.unusedNodes2.isEmpty()) { TreeNode tn = new TreeNode(this); int n = tn.unusedNodes1.size(); int e = 0; Iterator<Node> nodeIter = tn.unusedNodes1.iterator(); while (nodeIter.hasNext()) { Node node = nodeIter.next(); int i = tn.originalGraph1.indexOf(node); // find number of edges adjacent to node i e += this.getNumberOfAdjacentEdges(tn.matching,a1,i); tn.matching[i] = -2; // -2 = deletion } tn.addCost(n * this.cf.getNodeCosts()); tn.addCost(e * this.cf.getEdgeCosts() * factor); tn.unusedNodes1.clear(); successors.add(tn); } else { // there are still nodes in g2 but no nodes in g1, the nodes of // g2 are inserted if (this.unusedNodes1.isEmpty()) { TreeNode tn = new TreeNode(this); int n = tn.unusedNodes2.size(); int e = 0; Iterator<Node> nodeIter = tn.unusedNodes2.iterator(); while (nodeIter.hasNext()) { Node node = nodeIter.next(); int i = tn.originalGraph2.indexOf(node); // find number of edges adjacent to node i e += this.getNumberOfAdjacentEdges(tn.inverseMatching,a2,i); tn.inverseMatching[i] = -2; // -2 = insertion } tn.addCost(n * this.cf.getNodeCosts()); tn.addCost(e * this.cf.getEdgeCosts() * factor); tn.unusedNodes2.clear(); successors.add(tn); } else { // there are nodes in both g1 and g2 for (int i = 0; i < this.unusedNodes2.size(); i++) { TreeNode tn = new TreeNode(this); Node start = tn.unusedNodes1.remove(); Node end = tn.unusedNodes2.remove(i); tn.addCost(this.cf.getCost(start, end)); int startIndex = tn.originalGraph1.indexOf(start); int endIndex = tn.originalGraph2.indexOf(end); tn.matching[startIndex] = endIndex; tn.inverseMatching[endIndex] = startIndex; // edge processing this.processEdges(tn, start, startIndex, end, endIndex); successors.add(tn); } // deletion of a node from g_1 is also a valid successor TreeNode tn = new TreeNode(this); Node deleted = tn.unusedNodes1.remove(); int i = tn.originalGraph1.indexOf(deleted); tn.matching[i] = -2; // deletion tn.addCost(this.cf.getNodeCosts()); // find number of edges adjacent to node i int e = this.getNumberOfAdjacentEdges(tn.matching, a1, i); tn.addCost(this.cf.getEdgeCosts() * e * factor); successors.add(tn); } } return successors; } /** * TODO needs massive refactoring! * @param tn * @param start * @param startIndex * @param end * @param endIndex */ private void processEdges(TreeNode tn, Node start, int startIndex, Node end, int endIndex) { for (int e = 0; e < tn.a1[startIndex].length; e++) { if (tn.a1[startIndex][e] != null) { // there is an edge between start and start2 Edge edge = tn.a1[startIndex][e]; int start2Index = e; if (tn.matching[start2Index] != -1) { // other end has been handled int end2Index = tn.matching[start2Index]; if (end2Index >= 0) { if (tn.a2[endIndex][end2Index] != null) { Edge edge2 = tn.a2[endIndex][end2Index]; tn.addCost(this.cf.getCost(edge, edge2) * factor); } else { tn.addCost(this.cf.getEdgeCosts() * factor); } } else { // deletion tn.addCost(this.cf.getEdgeCosts() * factor); } } } else { // there is no edge between start and start2 int start2Index = e; if (tn.matching[start2Index] != -1) { // other "end" has been handled int end2Index = tn.matching[start2Index]; if (end2Index >= 0) { if (tn.a2[endIndex][end2Index] != null) { tn.addCost(this.cf.getEdgeCosts()* factor); } } } } // DUPLICATED CODE REFACTOR if (tn.a1[e][startIndex] != null) { Edge edge = tn.a1[e][startIndex]; int start2Index = e; if (tn.matching[start2Index] != -1) { // other end has been handled int end2Index = tn.matching[start2Index]; if (end2Index >= 0) { if (tn.a2[endIndex][end2Index] != null) { Edge edge2 = tn.a2[endIndex][end2Index]; tn.addCost(this.cf.getCost(edge, edge2) * factor); } else { tn.addCost(this.cf.getEdgeCosts() * factor); } } else { // deletion tn.addCost(this.cf.getEdgeCosts() * factor); } } } else { int start2Index = e; if (tn.matching[start2Index] != -1) { // other "end" has been handled int end2Index = tn.matching[start2Index]; if (end2Index >= 0) { if (tn.a2[endIndex][end2Index] != null) { tn.addCost(this.cf.getEdgeCosts()* factor); } } } } } } /** * @return number of adjacent edges of node with index @param i * NOTE: only edges (i,j) are counted if * j-th node hae been processed (deleted or substituted) */ private int getNumberOfAdjacentEdges(int[] m, Edge[][] a, int i) { int e= 0; for (int j = 0; j < a[i].length; j++){ if (m[j]!=-1){ // count edges only if other end has been processed if (a[i][j]!=null){ e += 1; } if (a[j][i]!=null){ e += 1; } } } return e; } /** * adds @param c * to the current solution cost */ private void addCost(double c) { this.cost += c; } /** * tree nodes are ordererd according to their past cost * in the open list: * NOTE THAT CURRENTLY NO HEURISTIC IS IMPLEMENTED FOR ESTIMATING THE FUTURE COSTS */ public int compareTo(TreeNode other) { if ((this.getCost() - other.getCost())<0){ return -1; } if ((this.getCost() - other.getCost())>0){ return 1; } // we implement the open list as a TreeSet which does not allow // two equal objects. That is, if two treenodes have equal cost, only one // of them would be added to open, which would not be desirable return 1; } /** * @return true if all nodes are used in the current solution */ public boolean allNodesUsed() { if (unusedNodes1.isEmpty() && unusedNodes2.isEmpty()) { return true; } return false; } /** * some getters and setters */ public Graph getUnusedNodes1() { return unusedNodes1; } public Graph getUnusedNodes2() { return unusedNodes2; } public double getCost() { return this.cost; } public CostFunction getCf() { return cf; } }