// Copy Function void ClusterGraph::shallowCopy(const ClusterGraph &C) { const Graph &G = C; m_pGraph = &G; initGraph(G); m_updateDepth = C.m_updateDepth; m_depthUpToDate = C.m_depthUpToDate; // Construct cluster tree ClusterArray<cluster> originalClusterTable(C); for(cluster c : C.clusters) { if (c == C.m_rootCluster) { originalClusterTable[c] = m_rootCluster; //does not really need to be assigned HERE in for m_rootCluster->m_depth = 1; continue; } originalClusterTable[c] = newCluster(); originalClusterTable[c]->m_depth = c->depth(); } for(cluster c : C.clusters) { if (c == C.m_rootCluster) continue; originalClusterTable[c]->m_parent = originalClusterTable[c->m_parent]; originalClusterTable[c->m_parent]->children.pushBack(originalClusterTable[c]); originalClusterTable[c]->m_it = originalClusterTable[c->m_parent]->getChildren().rbegin(); } for(node v : G.nodes) reassignNode(v,originalClusterTable[C.clusterOf(v)]); copyLCA(C); }
bool ClusterPlanarity::isClusterPlanar(const ClusterGraph &CG, List<nodePair> &addedEdges) { m_optStatus = Master::Optimal; // We first check if there is more to do then just checking planarity on the // input graph. // Simple shortcut: With < 5 vertices, no non-planarity is possible... bool result = isPlanar(CG.constGraph()); if (!result || (CG.numberOfClusters() == 1)) { // Either non-planar or only root cluster exists, which does not restrict c-planarity. return result; } // We first create a copy of input G, and work solely on the copy // In case of the sm_new solution method, we partition the graph in // independent parts and test them separately // For all parts we test until non-c-planar or all tested. if (m_solmeth==sm_new) { // We use the ClusterAnalysis to search for independent bags // Here is the idea: We detect all bags that are minimum wrt // cluster inclusion (i.e. if a cluster contains a cluster c with // a single bag, we don't add the cluster c itself) but do // not contain an outeractive vertex wrt to smallest containing cluster // (i.e. the cluster used in the definition of bag). // The clustered subgraphs induced by these bags can be tested independently, // as we can move them freely in the drawing area of their enclosing parent cluster. ClusterAnalysis ca(CG, true); //Compute all structures, indyBags too // We can solve the c-planarity testing for all indyBags independently, // and in case all are c-planar, also our input c-graph is c-planar. const int numIndyBags = ca.numberOfIndyBags(); GraphCopy** theGraphs = new GraphCopy * [numIndyBags]; //Stores copies for the bag graphs. #ifdef OGDF_DEBUG cout << "Number of IndyBags "<<numIndyBags<<"\n"; #endif Logger::slout() << "Number of IndyBags "<<numIndyBags<<"\n"; Array<List<node> > nodesInBag(numIndyBags); //Stores vertices for the bag graphs. const Graph & G = CG.constGraph(); for(node v : G.nodes){ nodesInBag[ca.indyBagIndex(v)].pushBack(v); } for (int i = 0; i < numIndyBags && m_optStatus == Master::Optimal; i++) { // Create underlying graph theGraphs[i] = new GraphCopy(); theGraphs[i]->createEmpty(G); // Judging from the interface and the description, there are two // methods in GraphCopy that allow to construct parts based on a // set of vertices, initByNodes and initByActiveNodes, where the // latter one seems to be appropriate and can be used with an // additional 3n work to initialize the NodeArray and mark the vertices. // However, even though the former is meant to be used for connected // components, it also works for set of connected components, and // an independent bag is such a creature. EdgeArray<edge> eCopy(G); theGraphs[i]->initByNodes(nodesInBag[i], eCopy); ClusterGraph bagCG(*theGraphs[i]); //node v; ClusterArray<List<node> > cNodes(CG); ClusterArray<List<cluster> > cChildren(CG); ClusterArray<cluster> cCopy(CG); // Run through all original vertices and store // lists of copies at each cluster that is part of the bag. // Note: We should not add an enclosing parent cluster below // root, i.e., when the root does only have a single child // and no vertices, we delete the child again. for(node u : nodesInBag[i]) { cluster ct = CG.clusterOf(u); cNodes[ct].pushBack(theGraphs[i]->copy(u)); // Check if we need to store the parent relation on the path // to the root. Indicator is: We have just added the first element. while ((ct != CG.rootCluster()) && (cNodes[ct].size() + cChildren[ct].size() == 1)) { cChildren[ct->parent()].pushBack(ct); ct = ct->parent(); } } // Create cluster structure // For each vertex in the indyBag we create the cluster path // to the bag root if necessary. // Now build the cluster structure top down // Lists of root are never both empty List<cluster> queue; queue.pushBack(ca.indyBagRoot(i)); cCopy[queue.front()] = bagCG.rootCluster(); while (!queue.empty()) { cluster c = queue.popFrontRet(); //vertices are assigned to root by construction if (cCopy[c] != bagCG.rootCluster()) { for(node u : cNodes[c]) bagCG.reassignNode(u, cCopy[c]); } for(cluster ci : cChildren[c]) { cCopy[ci] = bagCG.newCluster(cCopy[c]); queue.pushBack(ci); } } #ifdef OGDF_DEBUG Logger::slout() << "Created clustered graph for indy bag with "<<theGraphs[i]->numberOfNodes()<< " nodes and "<< bagCG.numberOfClusters()<<" clusters\n"; // Make sure the cluster structure is a rooted tree cluster t = bagCG.rootCluster(); int ccnt = 0; List<cluster> cqueue; cqueue.pushBack(t); while (!cqueue.empty()) { t = cqueue.popFrontRet(); for(cluster c : t->children) { cqueue.pushBack(c); } ccnt++; } OGDF_ASSERT(ccnt == bagCG.numberOfClusters()); string filename = string("IndySubcgraph") + to_string(i) + ".gml"; ClusterGraphAttributes CGA(bagCG); GraphIO::writeGML(CGA, filename); #endif //now the actual test, similar to the one below... if (theGraphs[i]->numberOfNodes() > 2) //could even start at 4... { makeParallelFreeUndirected(*theGraphs[i]); //Could do this while creating copy Logger::slout()<< "IndyBag of size n m c"<<theGraphs[i]->numberOfNodes()<< " "<< theGraphs[i]->numberOfEdges()<< " "<< bagCG.numberOfClusters()<<"\n"; List<nodePair> ae; //Todo: Add an interface here that allows to transfer bag and // activity information to the master, otherwise we have to // compute this info twice. bool imresult = doTest(bagCG, ae); #ifdef OGDF_DEBUG Logger::slout() << "IndyBag number "<<i<<" is "<< (imresult ? "" : "non-") <<"c-planar\n"; Logger::slout() << "Number of edges added for IndyBag: "<<ae.size()<<"\n"; #endif result = result && imresult; if (!result) return result; for(const nodePair &np : ae) { addedEdges.emplaceBack(theGraphs[i]->original(np.v1), theGraphs[i]->original(np.v2)); } } #ifdef OGDF_DEBUG else { Logger::slout() << "IndyBag number "<<i<<" skipped due to size\n"; } #endif }//for indy bags for (int i = 0; i < numIndyBags; i++) { delete theGraphs[i]; } delete [] theGraphs; // We test consistency by summing up the number of vertices. } else { //todo: can be joined again, as it is a special case wo cluster analysis //otherwise we just make a copy of the whole graph Graph G; ClusterArray<cluster> clusterCopy(CG); NodeArray<node> nodeCopy(CG.constGraph()); EdgeArray<edge> edgeCopy(CG.constGraph()); ClusterGraph C(CG,G,clusterCopy, nodeCopy, edgeCopy); makeParallelFreeUndirected(G); NodeArray<node> nodeOrig(G); for(node v : CG.constGraph().nodes) { nodeOrig[nodeCopy[v]] = v; } //Could use same list here for both graphs. addedEdges.clear(); List<nodePair> ae; result = doTest(C,ae); //nodepairs are for the copy, store original nodes here for (const nodePair &np : ae) { addedEdges.emplaceBack(nodeOrig[np.v1], nodeOrig[np.v2]); } } return result; }
//the call function that lets ClusterPlanarizationLayout compute a layout //for the input using \a weight for the computation of the cluster planar subgraph void ClusterPlanarizationLayout::call( Graph& G, ClusterGraphAttributes& acGraph, ClusterGraph& cGraph, EdgeArray<double>& edgeWeight, bool simpleCConnect) //default true { m_nCrossings = 0; bool subGraph = false; // c-planar subgraph computed? //check some simple cases if (G.numberOfNodes() == 0) return; //------------------------------------------------------------- //we set pointers and arrays to the working graph, which can be //the original or, in the case of non-c-planar input, a copy Graph* workGraph = &G; ClusterGraph* workCG = &cGraph; ClusterGraphAttributes* workACG = &acGraph; //potential copy of original if non c-planar Graph GW; //list of non c-planarity causing edges List<edge> leftEdges; //list of nodepairs to be connected (deleted edges) List<NodePair> leftWNodes; //store some information //original to copy NodeArray<node> resultNode(G); EdgeArray<edge> resultEdge(G); ClusterArray<cluster> resultCluster(cGraph); //copy to original NodeArray<node> orNode(G); EdgeArray<edge> orEdge(G); ClusterArray<cluster> orCluster(cGraph); for(node workv : G.nodes) { resultNode[workv] = workv; //will be set to copy if non-c-planar orNode[workv] = workv; } for(edge worke : G.edges) { resultEdge[worke] = worke; //will be set to copy if non-c-planar orEdge[worke] = worke; } for (cluster workc : cGraph.clusters) { resultCluster[workc] = workc; //will be set to copy if non-c-planar orCluster[workc] = workc; } //----------------------------------------------- //check if instance is clusterplanar and embed it CconnectClusterPlanarEmbed CCPE; //cccp bool cplanar = CCPE.embed(cGraph, G); List<edge> connectEdges; //if the graph is not c-planar, we have to check the reason and to //correct the problem by planarising or inserting connection edges if (!cplanar) { bool connect = false; if ( (CCPE.errCode() == CconnectClusterPlanarEmbed::nonConnected) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCConnected) ) { //we insert edges to make the input c-connected makeCConnected(cGraph, G, connectEdges, simpleCConnect); //save edgearray info for inserted edges for(edge e : connectEdges) { resultEdge[e] = e; orEdge[e] = e; } connect = true; CCPE.embed(cGraph, G); if ( (CCPE.errCode() == CconnectClusterPlanarEmbed::nonConnected) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCConnected) ) { cerr << "no correct connection made\n"<<flush; OGDF_THROW(AlgorithmFailureException); } }//if not cconnected if ((CCPE.errCode() == CconnectClusterPlanarEmbed::nonPlanar) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCPlanar)) { subGraph = true; EdgeArray<bool> inSubGraph(G, false); CPlanarSubClusteredGraph cps; if (edgeWeight.valid()) cps.call(cGraph, inSubGraph, leftEdges, edgeWeight); else cps.call(cGraph, inSubGraph, leftEdges); #ifdef OGDF_DEBUG // for(edge worke : G.edges) { // if (inSubGraph[worke]) // acGraph.strokeColor(worke) = "#FF0000"; // } #endif //--------------------------------------------------------------- //now we delete the copies of all edges not in subgraph and embed //the subgraph (use a new copy) //construct copy workGraph = &GW; workCG = new ClusterGraph(cGraph, GW, resultCluster, resultNode, resultEdge); //---------------------- //reinit original arrays orNode.init(GW, nullptr); orEdge.init(GW, nullptr); orCluster.init(*workCG, nullptr); //set array entries to the appropriate values for (node workv : G.nodes) orNode[resultNode[workv]] = workv; for (edge worke : G.edges) orEdge[resultEdge[worke]] = worke; for (cluster workc : cGraph.clusters) orCluster[resultCluster[workc]] = workc; //---------------------------------------------------- //create new ACG and copy values (width, height, type) workACG = new ClusterGraphAttributes(*workCG, workACG->attributes()); for (node workv : GW.nodes) { //should set same attributes in construction!!! if (acGraph.attributes() & GraphAttributes::nodeType) workACG->type(workv) = acGraph.type(orNode[workv]); workACG->height(workv) = acGraph.height(orNode[workv]); workACG->width(workv) = acGraph.width(orNode[workv]); } if (acGraph.attributes() & GraphAttributes::edgeType) { for (edge worke : GW.edges) { workACG->type(worke) = acGraph.type(orEdge[worke]); //all other attributes are not needed or will be set } } for(edge ei : leftEdges) { edge e = resultEdge[ei]; NodePair np; np.m_src = e->source(); np.m_tgt = e->target(); leftWNodes.pushBack(np); GW.delEdge(e); } CconnectClusterPlanarEmbed CCP; #ifdef OGDF_DEBUG bool subPlanar = #endif CCP.embed(*workCG, GW); OGDF_ASSERT(subPlanar); }//if not planar else { if (!connect) OGDF_THROW_PARAM(PreconditionViolatedException, pvcClusterPlanar); } }//if //if multiple CCs are handled, the connectedges (their copies resp.) //can be deleted here //now CCPE should give us the external face ClusterPlanRep CP(*workACG, *workCG); OGDF_ASSERT(CP.representsCombEmbedding()); const int numCC = CP.numberOfCCs(); //equal to one //preliminary OGDF_ASSERT(numCC == 1); // (width,height) of the layout of each connected component Array<DPoint> boundingBox(numCC); for (int ikl = 0; ikl < numCC; ikl++) { CP.initCC(ikl); CP.setOriginalEmbedding(); OGDF_ASSERT(CP.representsCombEmbedding()) Layout drawing(CP); //m_planarLayouter.get().setOptions(4);//progressive adjEntry ae = nullptr; //internally compute adjEntry for outer face //edges that are reinserted in workGraph (in the same //order as leftWNodes) List<edge> newEdges; m_planarLayouter.get().call(CP, ae, drawing, leftWNodes, newEdges, *workGraph); OGDF_ASSERT(leftWNodes.size()==newEdges.size()) OGDF_ASSERT(leftEdges.size()==newEdges.size()) ListConstIterator<edge> itE = newEdges.begin(); ListConstIterator<edge> itEor = leftEdges.begin(); while (itE.valid()) { orEdge[*itE] = *itEor; ++itE; ++itEor; } //hash index over cluster ids HashArray<int, ClusterPosition> CA; computeClusterPositions(CP, drawing, CA); // copy layout into acGraph // Later, we move nodes and edges in each connected component, such // that no two overlap. for(int i = CP.startNode(); i < CP.stopNode(); ++i) { node vG = CP.v(i); acGraph.x(orNode[vG]) = drawing.x(CP.copy(vG)); acGraph.y(orNode[vG]) = drawing.y(CP.copy(vG)); for(adjEntry adj : vG->adjEdges) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); edge orE = orEdge[eG]; if (orE) drawing.computePolylineClear(CP,eG,acGraph.bends(orE)); } }//for //even assignment for all nodes is not enough, we need all clusters for(cluster c : workCG->clusters) { int clNumber = c->index(); //int orNumber = originalClId[c]; cluster orCl = orCluster[c]; if (c != workCG->rootCluster()) { OGDF_ASSERT(CA.isDefined(clNumber)); acGraph.height(orCl) = CA[clNumber].m_height; acGraph.width(orCl) = CA[clNumber].m_width; acGraph.y(orCl) = CA[clNumber].m_miny; acGraph.x(orCl) = CA[clNumber].m_minx; }//if real cluster } // the width/height of the layout has been computed by the planar // layout algorithm; required as input to packing algorithm boundingBox[ikl] = m_planarLayouter.get().getBoundingBox(); }//for connected components //postProcess(acGraph); // // arrange layouts of connected components // Array<DPoint> offset(numCC); m_packer.get().call(boundingBox,offset,m_pageRatio); // The arrangement is given by offset to the origin of the coordinate // system. We still have to shift each node, edge and cluster by the offset // of its connected component. const Graph::CCsInfo &ccInfo = CP.ccInfo(); for(int i = 0; i < numCC; ++i) { const double dx = offset[i].m_x; const double dy = offset[i].m_y; HashArray<int, bool> shifted(false); // iterate over all nodes in ith CC for(int j = ccInfo.startNode(i); j < ccInfo.stopNode(i); ++j) { node v = ccInfo.v(j); acGraph.x(orNode[v]) += dx; acGraph.y(orNode[v]) += dy; // update cluster positions accordingly //int clNumber = cGraph.clusterOf(orNode[v])->index(); cluster cl = cGraph.clusterOf(orNode[v]); if ((cl->index() > 0) && !shifted[cl->index()]) { acGraph.y(cl) += dy; acGraph.x(cl) += dx; shifted[cl->index()] = true; }//if real cluster for(adjEntry adj : v->adjEdges) { if ((adj->index() & 1) == 0) continue; edge e = adj->theEdge(); //edge eOr = orEdge[e]; if (orEdge[e]) { DPolyline &dpl = acGraph.bends(orEdge[e]); for(DPoint &p : dpl) { p.m_x += dx; p.m_y += dy; } } } }//for nodes }//for numcc while (!connectEdges.empty()) { G.delEdge(connectEdges.popFrontRet()); } if (subGraph) { //originalClId.init(); orCluster.init(); orNode.init(); orEdge.init(); delete workCG; delete workACG; }//if subgraph created acGraph.removeUnnecessaryBendsHV(); }//call