All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jbpt.algo.tree.tctree.TCTree Maven / Gradle / Ivy

package org.jbpt.algo.tree.tctree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.jbpt.graph.abs.AbstractTree;
import org.jbpt.graph.abs.IEdge;
import org.jbpt.graph.abs.IGraph;
import org.jbpt.hypergraph.abs.IVertex;

/**
 * This class takes a biconnected graph and decomposes it into the tree of the triconnected components.

* * NOTE THAT GIVEN GRAPH MUST BE BICONNECTED; OTHERWISE RESULT IS UNEXPECTED.

* * Every triconnected component is either of a trivial, or POLYGON, or BOND, or RIGID type ({@link TCType}). * Note that every edge is a trivial component, but is not explicitly computed by this class. * This implementation is an adaption of the algorithm implemented by Martin Mader. * The general process of this decomposition is described in his master's thesis.

* * For more information on the algorithm please refer to: * Carsten Gutwenger and Petra Mutzel: A Linear Time Implementation of SPQR-Trees. Graph Drawing 2000: 77-90. * * @see {@link BiconnectivityCheck} for testing graph biconnectivity. * @see {@link TCType} provides enumeration for types of triconnected components. * * @param Edge template. * @param Vertex template. * * @author Martin Mader * @author Artem Polyvyanyy * @author Christian Wiggert * * @assumption Given graph is biconnected. */ public class TCTree, V extends IVertex> extends AbstractTree> { // Original graph to decompose protected IGraph graph = null; // Edge of the original graph to use as a back edge protected E backEdge = null; // Maps internal edges used for technical purpose to original graph edges private Map e2o = new HashMap(); /** * Constructor. * * @param graph A graph to decompose. */ public TCTree(IGraph graph) { if (graph==null) return; if (graph.getEdges().isEmpty()) return; this.graph = graph; this.backEdge = graph.getEdges().iterator().next(); this.construct(); } /** * Constructor. * * @param graph A graph to decompose. * @param backEdge An edge of the graph to use as a back edge. A triconnected component that contains the edge will become the root of the tree. */ public TCTree(IGraph graph, E backEdge) { if (graph==null) return; if (!graph.contains(backEdge)) return; this.graph = graph; this.backEdge = backEdge; this.construct(); } /** * Constructs the tree of the triconnected components. */ protected void construct() { Vector> components = new Vector>(); EdgeMap virtualEdgeMap = this.createEdgeMap(this.graph); virtualEdgeMap.initialiseWithFalse(); virtualEdgeMap.put(backEdge,true); EdgeMap assignedVirtEdgeMap = this.createEdgeMap(this.graph); EdgeMap isHiddenMap = this.createEdgeMap(this.graph); isHiddenMap.initialiseWithFalse(); MetaInfoContainer meta = new MetaInfoContainer(); meta.setMetaInfo(MetaInfo.VIRTUAL_EDGES, virtualEdgeMap); meta.setMetaInfo(MetaInfo.ASSIGNED_VIRTUAL_EDGES, assignedVirtEdgeMap); meta.setMetaInfo(MetaInfo.HIDDEN_EDGES, isHiddenMap); // discover triconnected components TCSkeleton mainSkeleton = new TCSkeleton(this.graph,this.e2o); this.splitOffInitialMultipleEdges(mainSkeleton,components,virtualEdgeMap,assignedVirtEdgeMap,isHiddenMap); this.findSplitComponents(mainSkeleton,components,virtualEdgeMap,assignedVirtEdgeMap,isHiddenMap,meta,backEdge.getV1()); // construct TCTreeNodes and TCSkeletons from components for (EdgeList el : components) { if (components.size()<=1) continue; TCTreeNode node = new TCTreeNode(); for (E edge : el) { if (virtualEdgeMap.getBool(edge)) node.skeleton.addVirtualEdge(edge.getV1(),edge.getV2(),edge.getId()); else node.skeleton.addEdge(edge.getV1(),edge.getV2(),this.e2o.get(edge)); } this.addVertex(node); } // classify triconnected components into polygons, bonds, and rigids this.classifyComponents(); // construct index Map>> ve2nodes = new HashMap>>(); this.indexComponents(ve2nodes); // merge bonds and polygons this.mergePolygonsAndBonds(ve2nodes); // assign names to components this.nameComponents(); // construct the tree of components this.constructTree(ve2nodes); } /** * Assign names to components. */ private void nameComponents() { int Pc, Bc, Rc; Pc = Bc = Rc = 0; for (TCTreeNode node : this.getVertices()) { if (node.getType()==TCType.BOND) node.setName("B"+Bc++); if (node.getType()==TCType.POLYGON) node.setName("P"+Pc++); if (node.getType()==TCType.RIGID) node.setName("R"+Rc++); } } /** * Compute index for faster tree construction. * @param ve2nodes Map to store index. */ private void indexComponents(Map>> ve2nodes) { for (TCTreeNode node : this.getVertices()) { for (E e : node.skeleton.getVirtualEdges()) { if (ve2nodes.get(e.getTag())==null){ Set> nodes = new HashSet>(); nodes.add(node); ve2nodes.put(e.getTag(),nodes); } else { ve2nodes.get(e.getTag()).add(node); } } } } /** * Merge polygons and bonds that share virtual edges. * @param ve2nodes Index. */ private void mergePolygonsAndBonds(Map>> ve2nodes) { Set toRemove = new HashSet(); for (Map.Entry>> entryA : ve2nodes.entrySet()) { Iterator> i = entryA.getValue().iterator(); TCTreeNode v1 = i.next(); TCTreeNode v2 = i.next(); if (v1.getType()!=v2.getType()) continue; if (v1.getType()==TCType.RIGID) continue; for (E e : v2.skeleton.getEdges()) { if (v2.skeleton.isVirtual(e)) { if (!e.getTag().equals(entryA.getKey())) v1.skeleton.addVirtualEdge(e.getV1(),e.getV2(),e.getTag()); } else v1.skeleton.addEdge(e.getV1(),e.getV2(),v2.skeleton.getOriginalEdge(e)); } Set ves = new HashSet(v1.skeleton.getVirtualEdges()); for(E ve : ves) { if (ve.getTag().equals(entryA.getKey())) v1.skeleton.removeEdge(ve); } for (Map.Entry>> entryB : ve2nodes.entrySet()) { if (entryB.getValue().contains(v2)) { entryB.getValue().remove(v2); entryB.getValue().add(v1); if (entryB.getValue().size()==1) toRemove.add(entryB.getKey()); } } this.removeVertex(v2); } for (Object ve : toRemove) ve2nodes.remove(ve); } /** * Construct the tree. * @param ve2nodes Index. */ private void constructTree(Map>> ve2nodes) { TCTreeNode tobeRoot = this.getVertices().size()==1 ? this.getVertices().iterator().next() : null; Set> visited = new HashSet>(); for (Map.Entry>> entry : ve2nodes.entrySet()) { Iterator> i = entry.getValue().iterator(); TCTreeNode v1 = i.next(); TCTreeNode v2 = i.next(); this.addEdge(v1,v2); if (tobeRoot==null && !visited.contains(v1)) if (this.checkRoot(v1)) tobeRoot = v1; visited.add(v1); if (tobeRoot==null && !visited.contains(v2)) if (this.checkRoot(v2)) tobeRoot = v2; visited.add(v2); } // construct trivial fragments for (TCTreeNode node : this.getVertices()) { for (E edge : node.getSkeleton().getOriginalEdges()) { TCTreeNode trivial = new TCTreeNode(); trivial.type = TCType.TRIVIAL; trivial.skeleton.addEdge(edge.getV1(), edge.getV2(), edge); trivial.setName(edge.toString()); this.addEdge(node,trivial); } } this.reRoot(tobeRoot); } /** * Check if triconnected component must be root of the tree. * * @param v Triconnected component. * @return true if the triconnected component contains the back edge; otherwise false. */ private boolean checkRoot(TCTreeNode v) { return v.skeleton.getOriginalEdges().contains(this.backEdge); } /** * Classify triconnected components into polygons, bonds, and rigids */ private void classifyComponents() { for (TCTreeNode n : this.getVertices()) { if (n.skeleton.countVertices()==2) { n.type = TCType.BOND; continue; } boolean isPolygon = true; Iterator vs = n.skeleton.getVertices().iterator(); while (vs.hasNext()) { V v = vs.next(); if (n.skeleton.getEdges(v).size()!=2) { isPolygon = false; break; } } if (isPolygon) n.type = TCType.POLYGON; else n.type = TCType.RIGID; } } /** * Runs the different DFS algorithms and creates the triconnected components based on the given graph and maps. */ @SuppressWarnings("unchecked") private void findSplitComponents(TCSkeleton graph, Vector> components, EdgeMap virtEdgeMap, EdgeMap assignedVirtEdgeMap, EdgeMap isHiddenMap, MetaInfoContainer meta, V root) { // initial adjacency map NodeMap adjMap = this.createNodeMap(graph); for (V v:graph.getVertices()){ EdgeList adj = new EdgeList(); for (E e:graph.getEdges(v)){ adj.add(e); } adjMap.put(v, adj); } meta.setMetaInfo(MetaInfo.DFS_ADJ_LISTS, adjMap); // first DFS -- calculate lowpoint information LowAndDescDFS dfs1 = new LowAndDescDFS(graph, meta, adjMap); dfs1.start(root); // order adjacency lists according to low-point values NodeMap orderedAdjMap = orderAdjLists(graph, meta); NodeMap copiedOrderedAdjMap = new NodeMap(); for (V node:orderedAdjMap.keySet()) { copiedOrderedAdjMap.put(node, ((EdgeList) orderedAdjMap.get(node)).clone()); } // second DFS -- renumber the vertices NumberDFS dfs2 = new NumberDFS(graph, meta, copiedOrderedAdjMap); dfs2.start(root); // workaround to circumvent a problem in the JBPT framework // which leads to not properly removed virtual edges in the TCTreeSkeleton // therefore this count is used to store the current state during dfs3 NodeMap edgeCount = new NodeMap(); for (V node:graph.getVertices()) { edgeCount.put(node, graph.getEdges(node).size()); } meta.setMetaInfo(MetaInfo.DFS_EDGE_COUNT, edgeCount); // third DFS -- find the actual split components SplitCompDFS dfs3 = new SplitCompDFS(graph, meta, copiedOrderedAdjMap, components, dfs2 .getParentMap(), dfs2.getTreeArcMap(), dfs2.getHighptMap(), dfs2.getEdgeTypeMap(), virtEdgeMap, assignedVirtEdgeMap, isHiddenMap); dfs3.start(root); } @SuppressWarnings("unchecked") private NodeMap orderAdjLists(IGraph graph, MetaInfoContainer meta) { Collection edges = graph.getEdges(); ArrayList> bucket = new ArrayList>(); int bucketSize = 3 * (graph.countVertices()) + 2; for (int i=0; i< bucketSize; i++){ bucket.add(new EdgeList()); } int phi; for (E e:edges) { phi = -1; // assign each edge its potential phi if (((EdgeMap) meta.getMetaInfo(MetaInfo.DFS_EDGE_TYPE)).getInt(e) == AbstractDFS.TREE_EDGE){ // e is tree edge if (((NodeMap) meta.getMetaInfo(MetaInfo.DFS_LOWPT2_NUM)).getInt(e.getV2()) < ((NodeMap) meta.getMetaInfo(MetaInfo.DFS_NUM)).getInt(e.getV1())){ // low2(w) < v phi = 3 * ((NodeMap) meta.getMetaInfo(MetaInfo.DFS_LOWPT1_NUM)).getInt(e.getV2()); } else { // low2(w) >= v phi = 3 * ((NodeMap) meta.getMetaInfo(MetaInfo.DFS_LOWPT1_NUM)).getInt(e.getV2()) + 2; } } else { // e is back edge phi = 3 * ((NodeMap) meta.getMetaInfo(MetaInfo.DFS_NUM)).getInt(e.getV2()) + 1; } // put edge into bucket according to phi // ! bucket's index start with 0 (bucket.get(phi-1)).add(e); } // create a new node map for the ordered adj list NodeMap orderedAdjMap = this.createNodeMap(graph); for (V node:graph.getVertices()) { EdgeList adj = new EdgeList(); orderedAdjMap.put(node, adj); } meta.setMetaInfo(MetaInfo.DFS_ORDERED_ADJ_LISTS, orderedAdjMap); // put edges into adj list according to order in buckets for (EdgeList el : bucket){ while (!el.isEmpty()){ E e = el.pop(); ((EdgeList) orderedAdjMap.get(e.getV1())).add(e); } } return orderedAdjMap; } /** * This method is used for the deletion of multiple edges. * The edges are sorted in a manner, so that multiple edges * are positioned consecutively in the returned EdgeList. */ @SuppressWarnings("unchecked") private EdgeList sortConsecutiveMultipleEdges(IGraph g){ NodeMap indices = new NodeMap(); int count = 0; for (V vertex:g.getVertices()) { indices.put(vertex, count++); } // bucketSort edges such that multiple edges come after each other ArrayList edges = (ArrayList) g.getEdges(); ArrayList> bucket = new ArrayList>(); // place edges into buckets according to vertex with smaller index for (int i = 0; i < g.getVertices().size(); i++) {//edges.size(); i++) { bucket.add(new EdgeList()); } for (E e:edges) { int i = Math.min((Integer) indices.get( e.getV1()), (Integer) indices.get(e.getV2())); bucket.get(i).add(e); } // sort within buckets according to enDP_NAMESoint with larger index EdgeList sortedEdges = new EdgeList(); for (EdgeList l : bucket){ HashMap> map = new HashMap>(); for (Object e : l){ // add up indices of enDP_NAMESoints Integer i = (Integer) indices.get(((E)e).getV1()) + (Integer) indices.get(((E)e).getV2()); // take this as key for the map EdgeList el = map.get(i); // and add the edge to the corresponding edge list if (el == null) { el = new EdgeList(); el.add((E) e); map.put(i, el); } else { el.add((E) e); } } // put edges into output list Collection> col = map.values(); for (EdgeList el : col){ if (el != null){ sortedEdges.addAll(el); } } } return sortedEdges; } /** * Simply deletes found multiple edges in the given graph. */ private void splitOffInitialMultipleEdges(TCSkeleton skeleton, Vector> components, EdgeMap virtEdgeMap, EdgeMap assignedVirtEdgeMap, EdgeMap isHiddenMap) { // sort edges such that multiple edges are consecutive EdgeList edges = this.sortConsecutiveMultipleEdges(skeleton); // split off multiple edge components EdgeList tempComp = new EdgeList(); E lastEdge=null, currentEdge=null; int tempCompSize = 0; for (E e : edges){ currentEdge = e; if (lastEdge != null){ // multiple edge if enDP_NAMESoint correspond to lastEdge's enDP_NAMESoints if ((currentEdge.getV1()==lastEdge.getV1() && currentEdge.getV2()==lastEdge.getV2()) || (currentEdge.getV1()==lastEdge.getV2() && currentEdge.getV2()==lastEdge.getV1())){ // add lastEdge to new component tempComp.add(lastEdge); tempCompSize++; } // current edge is different from last edge else{ // if tempCompSize is greater than zero, there has been split off // at least one edge, and the corresponding component needs to be // finished if (tempCompSize>0){ // add lastEdge to component tempComp.add(lastEdge); // finish component, i.e. add virtual edge and store the component this.newComponent(skeleton, components, tempComp, virtEdgeMap, assignedVirtEdgeMap, isHiddenMap, lastEdge.getV1(), lastEdge.getV2()); // look for new multiple edges next time tempComp = new EdgeList(); tempCompSize=0; } } } lastEdge = currentEdge; } // possible finishing of the last component due to multiple edges if (tempCompSize>0){ // add lastEdge to component tempComp.add(lastEdge); // finish component, i.e. add virtual edge and store the component this.newComponent(skeleton, components, tempComp, virtEdgeMap, assignedVirtEdgeMap, isHiddenMap, lastEdge.getV1(), lastEdge.getV2()); } } /** * Creates a new component based on the given list of contained edges. */ private void newComponent(TCSkeleton skeleton, Vector> components, EdgeList tempComp, EdgeMap virtEdgeMap, EdgeMap assignedVirtEdgeMap, EdgeMap isHiddenMap, V v1, V v2) { // remove edges from graph for (E e : tempComp) { skeleton.removeEdge(e); isHiddenMap.put(e, true); } // create virtual edge and add edges to component E virtualEdge = skeleton.addVirtualEdge(v1,v2); virtEdgeMap.put(virtualEdge, true); tempComp.add(0, virtualEdge); // assign virtual edge for(E e:tempComp) { assignedVirtEdgeMap.put(e, virtualEdge); } components.add(tempComp); } /** * Creates an edgeMap for the given graph containing all edges of the graph. * @param g Graph. */ private EdgeMap createEdgeMap(IGraph g) { EdgeMap map = new EdgeMap(); for (E e:g.getEdges()) { map.put(e, null); } return map; } /** * Creates a NodeMap for the given graph containing all nodes of the graph. * @param g Graph. * @return NodeMap. */ private NodeMap createNodeMap(IGraph g) { NodeMap map = new NodeMap(); for (V v:g.getVertices()) { map.put(v, null); } return map; } /** * Get original graph. * @return Original graph. */ public IGraph getGraph() { return this.graph; } /** * Get the triconnected components of a given {@link TCType} class. * @param {@link TCType} class. * @return Collection of the triconnected components of the given {@link TCType} class. */ public Collection> getTCTreeNodes(TCType type) { Set> result = new HashSet>(); for (TCTreeNode node : this.getVertices()) if (node.getType()==type) result.add(node); return result; } /** * Get the triconnected components. * @return Collection of the triconnected components. */ public Collection> getTCTreeNodes() { return this.getVertices(); } }