1 /** \file
2  * \brief Implementation of class ClusterAnalysis that calculates
3  * the bag and inner/outer activity structures for a clustered graph
4  * as described in Chimani, Klein:Shrinking the Search Space for Clustered
5  * Planarity. GD 2012.
6  *
7  * \author Karsten Klein
8  *
9  * \par License:
10  * This file is part of the Open Graph Drawing Framework (OGDF).
11  *
12  * \par
13  * Copyright (C)<br>
14  * See README.md in the OGDF root directory for details.
15  *
16  * \par
17  * This program is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU General Public License
19  * Version 2 or 3 as published by the Free Software Foundation;
20  * see the file LICENSE.txt included in the packaging of this file
21  * for details.
22  *
23  * \par
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  * GNU General Public License for more details.
28  *
29  * \par
30  * You should have received a copy of the GNU General Public
31  * License along with this program; if not, see
32  * http://www.gnu.org/copyleft/gpl.html
33  */
34 
35 #include <ogdf/cluster/ClusterAnalysis.h>
36 #include <ogdf/cluster/ClusterArray.h>
37 #include <ogdf/basic/Queue.h>
38 #include <ogdf/basic/DisjointSets.h>
39 
40 // Comment on use of ClusterArrays:
41 // We would like to save some space by only reserving one slot
42 // per existing cluster instead of maxClusterIndex() slots,
43 // which might be larger. However, we then would
44 // need to store an index with each cluster in a struct here,
45 // minimizing the effect again. Note ClusterArrays are static
46 // here, a change in the ClusterGraph won't be detected
47 // by ClusterAnalysis after initialization.
48 // Where multiple indexing is done, the static version
49 // of ClusterArrays is used with successive cluster index
50 // numbers computed locally, to save memory. When only a single
51 // indexed structure is needed this is clearly an overhead.
52 // Therefore the ClusterArrays are then created with size maxClusterIndex().
53 
54 
55 namespace ogdf {
56 
57 //Needs to be the largest int allowed, as it is used as default,
58 //and an update is done for smaller values
59 const int ClusterAnalysis::IsNotActiveBound = std::numeric_limits<int>::max();
60 const int ClusterAnalysis::DefaultIndex = -1;
61 
62 //Constructor
ClusterAnalysis(const ClusterGraph & C,bool oalists,bool indyBags)63 ClusterAnalysis::ClusterAnalysis(const ClusterGraph& C, bool oalists, bool indyBags) : m_C(&C), m_oanum(nullptr), m_ianum(nullptr),
64 		m_bags(nullptr), m_storeoalists(oalists), m_lcaEdges(nullptr), m_indyBags(indyBags), m_numIndyBags(-1), m_indyBagRoots(nullptr)
65 {
66 	init();
67 	computeBags();
68 	if (m_indyBags)
69 	{
70 		computeIndyBags();
71 	}
72 }
ClusterAnalysis(const ClusterGraph & C,bool indyBags)73 ClusterAnalysis::ClusterAnalysis(const ClusterGraph& C, bool indyBags): m_C(&C), m_oanum(nullptr), m_ianum(nullptr),
74 			m_bags(nullptr), m_storeoalists(true), m_lcaEdges(nullptr), m_indyBags(indyBags),m_numIndyBags(-1), m_indyBagRoots(nullptr)
75 {
76 	init();
77 	computeBags();
78 	// Even though it looks like we could compute bags and
79 	// indyBags in one pass, we do not look at each vertex
80 	// in each cluster during bag computation, as this would
81 	// be inefficient (use Union-Find instead). However, for
82 	// independent bags, we need to identify bags wo outeractive
83 	// vertices per cluster (TODO there may be shortcuts however, e.g.
84 	// if number of outeractive vertices is zero for a cluster, then
85 	// every bag is independent).
86 	if (m_indyBags)
87 	{
88 		computeIndyBags();
89 	}
90 }
~ClusterAnalysis()91 ClusterAnalysis::~ClusterAnalysis()
92 {
93 	cleanUp();
94 }
95 
cleanUp()96 void ClusterAnalysis::cleanUp()
97 {
98 	delete m_oanum;
99 	delete m_ianum;
100 	delete m_bags;
101 	delete m_lcaEdges;
102 	if (m_storeoalists) delete m_oalists;
103 	for(node v : m_C->constGraph().nodes)
104 	{
105 		delete m_bagindex[v];
106 	}
107 	if (m_indyBags) delete m_indyBagRoots;
108 }
109 
outerActive(cluster c) const110 int ClusterAnalysis::outerActive(cluster c) const {return (*m_oanum)[c];}
innerActive(cluster c) const111 int ClusterAnalysis::innerActive(cluster c) const {return (*m_ianum)[c];}
numberOfBags(cluster c) const112 int ClusterAnalysis::numberOfBags(cluster c) const {return (*m_bags)[c];}
oaNodes(cluster c)113 List<node>& ClusterAnalysis::oaNodes(cluster c) {
114 	OGDF_ASSERT(m_storeoalists);
115 	return (*m_oalists)[c];
116 }
117 
isOuterActive(node v,cluster c) const118 bool ClusterAnalysis::isOuterActive(node v, cluster c) const
119 {
120 	return (*m_oactive[v])[c] > 0;
121 }
isInnerActive(node v,cluster c) const122 bool ClusterAnalysis::isInnerActive(node v, cluster c) const
123 {
124 	return (*m_iactive[v])[c] > 0;
125 }
lcaEdges(cluster c)126 List<edge>& ClusterAnalysis::lcaEdges(cluster c)
127 {
128 	return (*m_lcaEdges)[c];
129 }
bagIndex(node v,cluster c)130 int ClusterAnalysis::bagIndex(node v, cluster c) {return (*m_bagindex[v])[c];}
131 
indyBagIndex(node v)132 int ClusterAnalysis::indyBagIndex(node v) {
133 	if (!m_indyBags)
134 	{
135 		OGDF_THROW_PARAM(AlgorithmFailureException,AlgorithmFailureCode::IllegalParameter);
136 	}
137 	return m_indyBagNumber[v];
138 
139 }
140 
indyBagRoot(int i)141 cluster ClusterAnalysis::indyBagRoot(int i) {
142 	if (!m_indyBags)
143 	{
144 		OGDF_THROW_PARAM(AlgorithmFailureException,AlgorithmFailureCode::IllegalParameter);
145 	}
146 	return m_indyBagRoots[i];
147 }
148 //we fill all arrays that store the inner/outer activity status
init()149 void ClusterAnalysis::init() {
150 //does it make sense to split into detecting inner / outer activity?
151 //is it faster to run once over the edges, with bidirectional detection
152 //and a check if the edge is processed already, or is it at least almost
153 //always as fast when testing from both directions?
154 
155 	//first outactive vertices
156 	const Graph &G = m_C->constGraph();
157 	m_iactive.init(G);
158 	m_oactive.init(G);
159 	m_ialevel.init(G, IsNotActiveBound);
160 	m_oalevel.init(G, IsNotActiveBound);
161 
162 	delete m_oanum;
163 	delete m_ianum;
164 	delete m_bags;
165 	m_oanum = new ClusterArray<int>(*m_C, 0);
166 	m_ianum = new ClusterArray<int>(*m_C, 0);
167 	m_bags = new ClusterArray<int>(*m_C, 0);
168 	m_lcaEdges = new ClusterArray<List<edge> >(*m_C);
169 	if (m_storeoalists)
170 		m_oalists = new ClusterArray<List<node> >(*m_C);
171 
172 	//We don't want to set dynamic depths update for clusters in m_C,
173 	//therefore we just compute the values here
174 	//top-down run through the cluster tree, depth 0 for the root
175 	ClusterArray<int> cdepth(*m_C);
176 	cdepth[m_C->rootCluster()] = 0;
177 	Queue<cluster> cq;
178 	for (cluster ci : m_C->rootCluster()->children) {
179 		cq.append(ci);
180 	}
181 
182 	while (!cq.empty())
183 	{
184 		cluster cc = cq.pop();
185 		cdepth[cc] = cdepth[cc->parent()]+1;
186 		for(cluster ci : cc->children) {
187 			cq.append(ci);
188 		}
189 	}
190 
191 	//store that we already visited e, as we don't have a static lookup
192 	//for the paths, running the search from both directions is slower.
193 	EdgeArray<bool> visited(G, false);
194 	for(node v : G.nodes)
195 	{
196 		// See comment on use of ClusterArrays above
197 		m_iactive[v] = new ClusterArray<int>(*m_C,0,m_C->maxClusterIndex()+1);
198 		m_oactive[v] = new ClusterArray<int>(*m_C,0,m_C->maxClusterIndex()+1);
199 	}
200 	for(node v : G.nodes)
201 	{
202 		for(adjEntry adj : v->adjEntries) {
203 			edge e = adj->theEdge();
204 
205 			if (!visited[e])
206 			{
207 				node w = e->opposite(v);
208 				List<cluster> el; // result cluster list of path in cluster tree T between v,w
209 				cluster c1,c2;   // ancestors of lca(v,w) on path, we don't really need them here
210 
211 				cluster lca = m_C->commonClusterAncestorsPath(v, w, c1, c2, el);
212 				OGDF_ASSERT(el.size() > 0);
213 				ListIterator<cluster> ctit =  el.begin();//m_C->clusterOf(v);
214 
215 				//run over the path, set activity status (vertices are
216 				//active for a cluster if adjacent edge crosses the border
217 
218 				//clusters before lca are left, i.e. v is outer active
219 				//clusters behind lca are entered, i.e. v is inner active
220 				while (ctit.valid() && (*ctit) != lca)
221 				{
222 					(*m_oactive[v])[*ctit]++;
223 					(*m_iactive[w])[*ctit]++;
224 
225 					//only count vertices a single time
226 					if ((*m_oactive[v])[*ctit] == 1) (*m_oanum)[*ctit]++;
227 					if ((*m_iactive[w])[*ctit] == 1) (*m_ianum)[*ctit]++;
228 
229 					//update the activity levels
230 					//could do this just for the last in the line...
231 					int clevel = cdepth[*ctit];
232 					if (m_ialevel[w] > clevel)
233 						m_ialevel[w] = clevel;
234 					if (m_oalevel[v] > clevel)
235 						m_oalevel[v] = clevel;
236 
237 					++ctit;
238 				}
239 
240 				OGDF_ASSERT((*ctit) == lca);
241 				//vertices are never active wrt lca
242 				//we store however the corresponding edges
243 				//for later use in bag detection
244 				(*m_lcaEdges)[lca].pushBack(e);
245 				++ctit;
246 
247 				while (ctit.valid())
248 				{
249 					(*m_iactive[v])[*ctit]++;
250 					(*m_oactive[w])[*ctit]++;
251 
252 					if ((*m_iactive[v])[*ctit] == 1) (*m_ianum)[*ctit]++;
253 					if ((*m_oactive[w])[*ctit] == 1) (*m_oanum)[*ctit]++;
254 
255 					//update the activity levels
256 					int clevel = cdepth[*ctit];
257 					//could do this just for the last in the line...
258 					if (m_ialevel[v] > clevel)
259 						m_ialevel[v] = clevel;
260 					if (m_oalevel[w] > clevel)
261 						m_oalevel[w] = clevel;
262 
263 					++ctit;
264 				}
265 
266 				visited[e] = true;
267 
268 #ifdef OGDF_DEBUG
269 				std::cout << "Edge "<< v << " " << w <<"\n";
270 #endif
271 			}
272 		}
273 
274 	}
275 #ifdef OGDF_DEBUG
276 	for(node v : G.nodes)
277 	{
278 		std::cout << "Knoten "<<v<<" ist";
279 		List<cluster> ol;
280 		List<cluster> il;
281 		for(cluster c : m_C->clusters)
282 		{
283 			if ((*m_iactive[v])[c]>0) il.pushBack(c);
284 			if ((*m_oactive[v])[c]>0) ol.pushBack(c);
285 		}
286 		std::cout << " inneractive for ";
287 		for (cluster ci : il) {
288 			std::cout << ci << ", ";
289 		}
290 		std::cout << "\n";
291 		std::cout << " outeractive for ";
292 		for (cluster ci : ol) {
293 			std::cout << ci << ", ";
294 		}
295 		std::cout << "\n";
296 	}
297 
298 #endif
299 }
300 
301 // Runs through a list of vertices (starting with the one \p nodeIT points to)
302 // which is expected to be a full list of cluster vertices in \p c. Depending on
303 // outer activity and bag index number of the vertices, independent bags
304 // are detected and a corresponding index is assigned accordingly for each vertex.
partitionCluster(ListConstIterator<node> & nodeIt,cluster c,HashArray<int,List<node>> & bagNodes,HashArray<int,bool> & indyBag,Skiplist<int * > & indexNumbers,Array<cluster> & bagRoots)305 void ClusterAnalysis::partitionCluster(ListConstIterator<node> & nodeIt, cluster c,
306 		HashArray<int, List<node> > & bagNodes, HashArray<int, bool> & indyBag,
307 		Skiplist<int*> & indexNumbers, Array<cluster> & bagRoots) {
308 	// Run through all vertices in c
309 	while (nodeIt.valid())
310 	{
311 		node v = *nodeIt;
312 		//if vertex is outeractive, the containing bag loses its status
313 		// Nodes that are already processed can never be outeractive
314 		// as we traverse bottom up. They are skipped as they are part
315 		// of an independent bag in an already processed cluster.
316 		if (m_indyBagNumber[v] == DefaultIndex)
317 		{
318 			int ind = bagIndex(v,c);
319 			if (isOuterActive(v,c))
320 			{
321 				indyBag[ind]= false;
322 			} else //don't need to add the index if vertex is oactive
323 			{
324 				if (!indexNumbers.isElement(&ind))
325 				{
326 					indexNumbers.add(new int(ind));
327 				}
328 				bagNodes[ind].pushBack(v); //store vertex in index' list
329 			}
330 		}
331 #ifdef OGDF_DEBUG
332 		if (m_indyBagNumber[v] != DefaultIndex)
333 			OGDF_ASSERT(!isOuterActive(v,c));
334 #endif
335 
336 		++nodeIt;
337 	}
338 	// Now we have all indexes of bags that don't solely contain oactive vertices.
339 	// For each index we check if the bag still has independency status,
340 	// in this case we have found an independent bag and can remove all its
341 	// vertices (mark them).
342 
343 #if 0
344 	std::cout << "Checking skiplist\n";
345 #endif
346 	SkiplistIterator<int*> its = indexNumbers.begin();
347 	while (its.valid())
348 	{
349 		int bind = *(*its);
350 #if 0
351 		std::cout << "Found index "<< bind <<"\n";
352 #endif
353 		if (indyBag[bind])
354 		{
355 #ifdef OGDF_DEBUG
356 			Logger::slout()  << "Found independent bag with "<< bagNodes[bind].size() << "vertices\n";
357 #endif
358 
359 			for (node v : bagNodes[bind]) {
360 				// Assign the final index number
361 				m_indyBagNumber[v] = m_numIndyBags;
362 			}
363 			bagRoots[m_numIndyBags] = c;
364 			m_numIndyBags++;
365 		}
366 
367 		delete *its;
368 		++its;
369 	}
370 }
371 
372 // For each cluster we check if we can identify an independent
373 // bag, which might be useful for clustered planarity testing.
374 // compute independent bag affiliation for all vertices,
375 // store result in m_indyBagNumber, and set m_numIndyBags accordingly.
376 // We could interweave this with computeBags to be more efficient but
377 // this would it make the code more complex and in case we don't need
378 // the IndyBags we would do additional work in vain.
computeIndyBags()379 void ClusterAnalysis::computeIndyBags() {
380 	m_numIndyBags = 0; //used both to count the bags and to store current
381 					   //indyBag index number for vertex assignment (i.e., starts with 1)
382 	const Graph &G = m_C->constGraph();
383 
384 	// Store the root cluster of each indyBag
385 	delete m_indyBagRoots;
386 	// Intermediate storage during computation, maximum of #vertices possible
387 #ifdef OGDF_DEBUG
388 	Array<cluster> bagRoots(0,G.numberOfNodes(),nullptr);
389 #else
390 	Array<cluster> bagRoots(G.numberOfNodes());
391 #endif
392 
393 	// Store indyBag affiliation. Every vertex will get a number != -1 (DefaultIndex,
394 	// as in the worst case the whole graph is an indyBag (in root cluster).
395 	// Once assigned, the number won't change during the processing.
396 	m_indyBagNumber.init(G, DefaultIndex);
397 
398 	// We run bottom up over all clusters (to find the minimum inclusion).
399 	// For each vertex, we use the outer activity and bag index information.
400 	// In case we find a bag without outeractive vertices it is a IndyBag.
401 	// Already processed vertices are simply marked by an indyBag index entry different to -1.
402 #if 0
403 	NodeArray<bool> processed(G, false); //mark vertices when IndyBag detected
404 #endif
405 
406 	// We do not have the sets of vertices for all bags, as only the bag index
407 	// has been stored for a cluster.
408 	// Detect the current leaf clusters for bottom up traversal.
409 	List<cluster> ccleafs;
410 	ClusterArray<int> unprocessedChildren(*m_C); //processing below: compute bags
411 	for(cluster c : m_C->clusters)
412 	{
413 		if (c->cCount() == 0) ccleafs.pushBack(c);
414 		unprocessedChildren[c] = c->cCount();
415 	}
416 	OGDF_ASSERT(!ccleafs.empty());
417 
418 	// Run through all clusters, leaves first.
419 	while (!ccleafs.empty()){
420 		// We cannot store the following information over the whole
421 		// graph even though the bag index (which is one out of the
422 		// set of vertex set ids in union find) would allow this.
423 		// The bag index is defined per cluster.
424 		// However, when moving up in the cluster tree indy information
425 		// is not monotone, we therefore would need to update it accordingly.
426 		// (Bag which is not outeractive will not become outeractive, but
427 		// may get a part of an outeractive bag with the same id, and an
428 		// outeractive bag might become enclosed).
429 		HashArray<int, bool> indyBag(true); // true if bag with index i does not have outeractive vertices
430 		// We want to store all vertices for each index that may be a
431 		// potential indyBag index. We could add these in the entry stored
432 		// in our index Skiplist, but then we need a comparison of the
433 		// respecting class objects, slowing down the processing.
434 		HashArray<int, List<node> > bagNodes; //holds vertices for index i
435 
436 		// We need a data structure that holds all indexes used, allows
437 		// to search for them and to iterate through them. Could use
438 		// insertion sorted dynamically allocated array here, but as
439 		// SkipLists are implemented in OGDF, which provide comparable
440 		// efficiency, we use them.
441 		Skiplist<int*> indexNumbers; //holds all index numbers in c
442 
443 		cluster c = ccleafs.popFrontRet();
444 
445 		List<node> nodes;
446 		ListConstIterator<node> it;
447 		// Process leaves separately. As long as we don't do something
448 		// special for non-leaves, we could just use getClusterNodes instead.
449 		if (c->cCount() == 0) {
450 			it = c->nBegin();
451 		}
452 		else {
453 			// At this point all child clusters of c have been processed.
454 			// This is somewhat slow, as we repeatedly collect the vertices
455 			// Todo: Instead, we could clone the clusternodes code
456 			// here and process them as we see them.
457 
458 			c->getClusterNodes(nodes);
459 
460 #if 0
461 			std::cout <<"Processing cluster with "<<nodes.size()<<"\n";
462 #endif
463 
464 			it = nodes.begin();
465 		}
466 		// Run through all vertices in c
467 		partitionCluster(it, c, bagNodes, indyBag, indexNumbers, bagRoots);
468 		// Now we update the status of the parent cluster and,
469 		// in case all its children are processed, add it to
470 		// the process queue.
471 		if (c != m_C->rootCluster())
472 		{
473 			OGDF_ASSERT(unprocessedChildren[c->parent()] > 0);
474 			unprocessedChildren[c->parent()]--;
475 			if (unprocessedChildren[c->parent()] == 0) ccleafs.pushBack(c->parent());
476 		}
477 	}
478 	// Copying into smaller array is a bit slower than just reserving the whole #v array,
479 	// but we do it anyway.
480 	m_indyBagRoots = new cluster[m_numIndyBags];
481 	for (int k = 0; k < m_numIndyBags; k++)
482 	{
483 		OGDF_ASSERT(bagRoots[k] != nullptr);
484 		m_indyBagRoots[k] = bagRoots[k];
485 	}
486 #ifdef OGDF_DEBUG
487 #if 0
488 	List<node> rnodes;
489 	m_C->rootCluster()->getClusterNodes(rnodes);
490 
491 	for(node v : rnodes) {
492 		std::cout << "Root bag index: "<<bagIndex(v, m_C->rootCluster())<<"\n";
493 		std::cout << "Indy bag index: "<<m_indyBagNumber[v]<<"\n";
494 	}
495 #endif
496 
497 	Skiplist<int*> ibind;
498 	for(node v : G.nodes)
499 	{
500 		int i = m_indyBagNumber[v];
501 		if (!ibind.isElement(&i))
502 		{
503 			ibind.add(new int(i));
504 		}
505 		std::cout << "numIndyBags: "<<m_numIndyBags<<" i: "<<i<<"\n";
506 		OGDF_ASSERT(i != DefaultIndex);
507 		OGDF_ASSERT(i >= 0);
508 		OGDF_ASSERT(i < m_numIndyBags);
509 
510 	}
511 	int storedBags = 0;
512 	SkiplistIterator<int*> its = ibind.begin();
513 	while (its.valid())
514 	{
515 		storedBags++;
516 		delete *its;
517 		++its;
518 	}
519 	Logger::slout() << m_numIndyBags<< " independent bags detected, "<<storedBags<<" stored\n";
520 	OGDF_ASSERT(m_numIndyBags==storedBags);
521 #endif
522 }
523 
524 //compute bag affiliation for all vertices
525 //store result in m_bagindex
computeBags()526 void ClusterAnalysis::computeBags() {
527 	const Graph &G = m_C->constGraph();
528 
529 	// Storage structure for results
530 	m_bagindex.init(G);
531 	// We use Union-Find for chunks and bags
532 	DisjointSets<> uf;
533 	NodeArray<int> setid(G); // Index mapping for union-find
534 #if 0
535 	node* nn = new node[G.numberOfNodes()]; // dito
536 #endif
537 
538 	// Every cluster gets its index
539 	ClusterArray<int> cind(*m_C);
540 	// We store the lists of cluster vertices
541 	List<node>* clists = new List<node>[m_C->numberOfClusters()];
542 	int i = 0;
543 
544 	// Store index and detect the current leaf clusters
545 	List<cluster> ccleafs;
546 	ClusterArray<int> unprocessedChildren(*m_C); //processing below: compute bags
547 	for(cluster c : m_C->clusters)
548 	{
549 		cind[c] = i++;
550 		if (c->cCount() == 0) ccleafs.pushBack(c);
551 		unprocessedChildren[c] = c->cCount();
552 	}
553 
554 
555 	// Now we run through all vertices, storing them in the parent lists,
556 	// at the same time, we initialize m_bagindex
557 	for(node v : G.nodes)
558 	{
559 		// setid is constant in the following
560 		setid[v] = uf.makeSet();
561 		// Each vertex v gets its own ClusterArray that stores v's bag index per cluster.
562 		// See comment on use of ClusterArrays above
563 		m_bagindex[v] = new ClusterArray<int>(*m_C,DefaultIndex, m_C->maxClusterIndex()+1);//m_C->numberOfClusters());
564 		cluster c = m_C->clusterOf(v);
565 		// Push vertices in parent list
566 		clists[cind[c]].pushBack(v);
567 	}
568 
569 	// Now each clist contains the direct vertex descendants
570 	// We process the clusters bottom-up, compute the chunks
571 	// of the leafs first. At each level, for a cluster the
572 	// vertex lists of all children are concatenated
573 	// (could improve this by having an array of size(#leafs)
574 	// and concatenating only at child1), then the bags are
575 	// updated as follows: chunks may be linked by exactly
576 	// the edges with lca(c) ie the ones in m_lcaEdges[c],
577 	// and bags may be built by direct child clusters that join chunks.
578 	// While concatenating the vertex lists, we can check
579 	// for the vertices in each child if the uf number is the same
580 	// as the one of a first initial vertex, otherwise we join.
581 
582 	// First, lowest level clusters are processed: All chunks are bags
583 
584 
585 	OGDF_ASSERT(!ccleafs.empty());
586 
587 	while (!ccleafs.empty()){
588 		const cluster c = ccleafs.popFrontRet();
589 		Skiplist<int*> cbags; //Stores bag indexes ocurring in c
590 
591 		auto storeResult = [&] {
592 			for (node v : clists[cind[c]]) {
593 				int theid = uf.find(setid[v]);
594 				(*m_bagindex[v])[c] = theid;
595 				if (!cbags.isElement(&theid)) {
596 					cbags.add(new int(theid));
597 				}
598 				// push into list of outer active vertices
599 				if (m_storeoalists && isOuterActive(v, c)) {
600 					(*m_oalists)[c].pushBack(v);
601 				}
602 			}
603 			(*m_bags)[c] = cbags.size(); // store number of bags of c
604 		};
605 
606 		if (m_storeoalists){
607 			//no outeractive vertices detected so far
608 			(*m_oalists)[c].clear();
609 		}
610 
611 		//process leafs separately
612 		if (c->cCount() == 0) {
613 
614 
615 			//Todo could use lcaEdges list here too, see below
616 			for (node u : c->nodes)
617 			{
618 				for(adjEntry adj : u->adjEntries) {
619 					node w = adj->twinNode();
620 					if (m_C->clusterOf(w) == c)
621 					{
622 						uf.link(uf.find(setid[u]),uf.find(setid[w]));
623 					}
624 				}
625 			}
626 			// Now all chunks in the leaf cluster are computed
627 			// update for parent is done in the else case
628 
629 			storeResult();
630 		}
631 		else {
632 			// ?We construct the vertex list by concatenating
633 			// ?the lists of the children to the current list.
634 			// We need the lists for storing the results efficiently.
635 			// (Should be slightly faster than to call clusterNodes each time)
636 			// Bags are either links of chunks by edges with lca==c
637 			// or links of chunk by child clusters.
638 			// Edge links
639 			for(edge e : (*m_lcaEdges)[c]) {
640 				uf.link(uf.find(setid[e->source()]),uf.find(setid[e->target()]));
641 			}
642 
643 			// Cluster links
644 			for(cluster cc : c->children)
645 			{
646 				//Initial id per child cluster cc: Use value of first
647 				//vertex, each time we encounter a different value in cc,
648 				//we link the chunks
649 
650 				//add (*itcc)'s vertices to c's list
651 				ListConstIterator<node> itvc = clists[cind[cc]].begin();
652 				int inid;
653 				if (itvc.valid()) inid = uf.find(setid[*itvc]);
654 				while (itvc.valid())
655 				{
656 					int theid = uf.find(setid[*itvc]);
657 
658 					if (theid != inid)
659 						uf.link(inid,theid);
660 					clists[cind[c]].pushBack(*itvc);
661 					++itvc;
662 				}
663 			}
664 
665 			storeResult();
666 		}
667 		// Now we update the status of the parent cluster and,
668 		// in case all its children are processed, add it to
669 		// the process queue.
670 		if (c != m_C->rootCluster())
671 		{
672 			OGDF_ASSERT(unprocessedChildren[c->parent()] > 0);
673 			unprocessedChildren[c->parent()]--;
674 			if (unprocessedChildren[c->parent()] == 0) ccleafs.pushBack(c->parent());
675 		}
676 	}
677 
678 	// clean up
679 	delete[] clists;
680 }
681 
682 
683 }
684