File Coverage

blib/lib/Graph/ModularDecomposition.pm
Criterion Covered Total %
statement 281 291 96.5
branch 168 176 95.4
condition 17 21 80.9
subroutine 22 22 100.0
pod 17 17 100.0
total 505 527 95.8


line stmt bran cond sub pod time code
1             package Graph::ModularDecomposition;
2              
3 18     18   240182 use 5.006;
  18         79  
  18         2060  
4 18     18   120 use strict;
  18         38  
  18         708  
5 18     18   169 use warnings;
  18         43  
  18         1350  
6              
7             =head1 NAME
8              
9             Graph::ModularDecomposition - Modular decomposition of directed graphs
10              
11             =cut
12              
13             require Exporter;
14             our $VERSION = '0.13';
15              
16 18     18   31503 use Graph 0.20105;
  18         7863847  
  18         7008  
17             require Graph::Directed;
18              
19             # NB! Exporter must come before Graph::Directed in @ISA
20             our @ISA = qw(Exporter Graph::Directed);
21              
22             # This allows declaration use Graph::ModularDecomposition ':all';
23             # may want tree_to_string, should move into own Tree::... module some day
24             # other exports are most likely for internal use only
25             # all other functions should be accessed as methods
26             our %EXPORT_TAGS = ( 'all' => [ qw(
27             setminus
28             setunion
29             pairstring_to_graph
30             partition_to_string
31             tree_to_string
32             ) ] );
33              
34             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
35              
36             our @EXPORT = qw(
37             );
38              
39             =head1 SYNOPSIS
40              
41             use Graph::ModularDecomposition qw(pairstring_to_graph tree_to_string);
42             my $g = new Graph::ModularDecomposition;
43              
44             my $h = $g->pairstring_to_graph( 'ab,ac,bc' );
45             print "yes\n" if check_transitive( $h );
46             print "yes\n" if $h->check_transitive; # same thing
47             my $m = $h->modular_decomposition_EGMS;
48             print tree_to_string( $m );
49              
50              
51             =head1 DESCRIPTION
52              
53             This module extends L by providing
54             new methods related to modular decomposition.
55              
56             The most important new method is modular_decomposition_EGMS(), which
57             for a directed graph with n vertices finds the modular decomposition
58             tree of the graph in O(n^2) time. Method tree_to_string() may be
59             useful to represent the decomposition tree in a friendlier format;
60             this needs to be explicitly imported.
61              
62             If you need to decompose an undirected graph, represent it as a
63             directed graph by adding two directed edges for each undirected edge.
64              
65             The method classify() uses the modular decomposition tree to classify
66             a directed graph as non-transitive, or for transitive digraphs,
67             as series-parallel (linear or parallel modules only), decomposable
68             (not series-parallel, but with at least one non-primitive module),
69             indecomposable (primitive), decomposable but consisting of primitive
70             or series modules only (only applies to graphs of at least 7 vertices),
71             or unclassified (should never apply).
72              
73             =head2 RELATED WORK
74              
75             Several recent graph algorithms have used the modular decomposition
76             tree as a basic building block. A simple example application of
77             these routines is to construct and search the modular decomposition
78             tree of a directed graph to determine if it is node-series-parallel.
79             Checking if a digraph is series-parallel can also be determined using
80             the O(m+n) Valdes-Tarjan-Lawler algorithm published in 1982, but this
81             only constructs a decomposition tree if the input is series-parallel:
82             other inputs are simply classified as non-series-parallel.
83              
84             The code here is based on algorithm 6.1 for modular decomposition of
85             two-structures, from
86              
87             A. Ehrenfeucht, H. N. Gabow, R. M. McConnell, and S. J. Sullivan, "An
88             O(n^2) Divide-and-Conquer Algorithm for the Prime Tree Decomposition
89             of Two-Structures and Modular Decomposition of Graphs", Journal of
90             Algorithms 16 (1994), pp. 283-294.
91              
92             I am not aware of any other publicly available implementations.
93             Any errors and omissions are of course my fault. Better algorithms
94             are known: O(m+n) run-time can be achieved using sophisticated data
95             structures (where m is the number of edges in the graph). For a
96             recent discussion of the history of modular decomposition, see
97              
98             E. Dahlhaus, J. Gustedt and R. M. McConnell, "Partially Complemented
99             Representations of Digraphs", Discrete Mathematics and Theoretical
100             Computer Science 5 (2002), pp. 147-168.
101              
102              
103             =head2 EXPORT
104              
105             None by default. Methods tree_to_string() and partition_to_string()
106             can be imported. Methods setminus() and setunion() are for internal
107             use but can also be imported.
108              
109              
110             =head2 METHODS
111              
112             =over 4
113              
114             =item debug()
115              
116             my $g = new Graph::ModularDecomposition;
117             Graph::ModularDecomposition->debug(1); # turn on debugging
118             Graph::ModularDecomposition->debug(2); # extra debugging
119             $g->debug(2); # same thing
120             $g->debug(0); # off (default)
121              
122             Manipulates the debug level of this module. Debug output is sent
123             to STDERR. Object-level debugging is not yet supported.
124              
125             =cut
126              
127 18     18   220 use Carp;
  18         2396  
  18         109674  
128              
129             my $VSEP = '|'; # string used to separate vertices
130             my $WSEP = '\|'; # regexp used to separate vertices
131             my $PSEP = '\+'; # regexp used to separate elements of partition
132             my $QSEP = '+'; # string used to separate elements of partition
133              
134             my $Debug = 0;
135              
136             sub debug {
137 25     25 1 9194 my $class = shift;
138 25 100       112 if ( ref($class) ) { $class = ref($class) }
  13         35  
139 25         46 $Debug = shift;
140 25 100       7716 carp 'Turning ', ($Debug ? 'on' : 'off'), ' ',
    100          
141             $class, ' debugging', ($Debug ? ", level $Debug" : '');
142             }
143              
144              
145             =item canonical_form()
146              
147             my $g = new Graph::ModularDecomposition;
148             Graph::ModularDecomposition->canonical_form(1); # on (default)
149             Graph::ModularDecomposition->canonical_form(0); # turn it off
150             $g->canonical_form(1); # same thing
151             $g->canonical_form(0); # off
152             print "yes" if $g->canonical_form();
153              
154             Manipulates whether this module keeps modular decomposition trees in
155             "canonical" form, where lists of vertices are kept sorted. This allows
156             tree_to_string() on two isomorphic decomposition trees to produce the
157             same output (well, sometimes -- a more general solution requires an
158             isomorphism test). Canonical form forces sorting of vertices in several
159             places, which will slow down some of the algorithms. When called with
160             no arguments, returns the current state.
161              
162             =cut
163              
164             my $Canonical_form = 0;
165              
166             sub canonical_form {
167 166     166 1 533 my $class = shift;
168 166 50       451 if ( ref($class) ) { $class = ref($class) }
  166         292  
169 166         235 my $cf = shift;
170 166 100       702 return $Canonical_form unless defined $cf;
171 1         2 $Canonical_form = $cf;
172             }
173              
174              
175             =item new()
176              
177             my $g = new Graph::ModularDecomposition;
178             $g = Graph::ModularDecomposition->new; # same thing
179             my $h = $g->new;
180              
181             Constructor. The instance method style C<$object->new> is an extension
182             and was not present in L.
183              
184             =cut
185              
186             sub new {
187 388     388 1 219497 my $self = shift;
188 388 100       1037 my $class = ref($self) ? ref($self) : $self;
189 388         2315 return bless $class->SUPER::new(@_), $class;
190             }
191              
192              
193             =item pairstring_to_graph
194              
195             my $g = Graph::ModularDecomposition
196             ->pairstring_to_graph( 'ac, ad, bd' );
197             my $h = $g->pairstring_to_graph( 'a-c, a-d,b-d' ); # same thing
198             my $h = $g->pairstring_to_graph( 'a,b,c,d,a-c,a-d,b-d' ); # same thing
199              
200             use Graph::ModularDecomposition qw( pairstring_to_graph );
201             my $k = pairstring_to_graph( 'Graph::ModularDecomposition',
202             'ac,ad,bd' ); # same thing
203              
204             Convert string of pairs input to Graph::ModularDecomposition output.
205             Allows either 'a-b,b-c,d' or 'ab,bc,d' style notation but these should
206             not be mixed in one string. Vertex labels should not include the
207             '-' character. Use the '-' style if multi-character vertex labels
208             are in use. Single label "pairs" are interpreted as vertices to add.
209              
210             =cut
211              
212             sub pairstring_to_graph {
213 28     28 1 4508 my $class = shift;
214 28 100       110 if ( ref($class) ) { $class = ref($class) }
  5         13  
215 28         51 my $pairs = shift;
216 28         85 my $g = new $class;
217 28         5832 my ($p, $q);
218 28 100       158 my $s = ( ( index( $pairs, '-' ) >= 0 ) ? '\-' : '' );
219 28         372 foreach my $r ( split /,\s*/, $pairs ) {
220 261         19563 ( $p, $q ) = split $s, $r;
221 261 100       9821 print "p=$p, q=$q\n" if $Debug > 2;
222 261 100       606 if ( $q ) {
223 254 100       808 $g = $g->add_edge( $p, $q ) unless $g->has_edge( $p, $q );
224             } else {
225 7 100       43 $g = $g->add_vertex( $p ) unless $g->has_vertex( $p );
226             }
227             }
228 28         1959 return bless $g, $class;
229             }
230              
231              
232             =item check_transitive()
233              
234             my $g = new Graph::ModularDecomposition;
235             # add some edges...
236             print "transitive" if $g->check_transitive;
237              
238             Returns 1 if input digraph is transitive, '' otherwise. May break if
239             Graph::stringify lists vertices in unsorted order.
240              
241             =cut
242              
243             sub check_transitive {
244 39     39 1 63 my $g = shift;
245 39         194 my $g2 = $g->copy;
246 39         56189 my $h = $g->TransitiveClosure_Floyd_Warshall;
247             # get rid of loops
248 39         146064 foreach ( $h->vertices ) { $h->delete_edge( $_, $_ ) }
  139         12648  
249 39         3592 foreach ( $g2->vertices ) { $g2->delete_edge( $_, $_ ) }
  139         5899  
250 39 100       1552 print STDERR "gdct: ", $g, ' vs. ', $h, "\n" if $Debug;
251 39         5726 return $h eq $g2;
252             }
253              
254              
255             =item setminus()
256              
257             my @d = setminus( ['a','b','c'], ['b','d'] ); # ('a','c')
258              
259             Given two references to lists, returns the set difference of the two
260             lists as a list. Can be imported.
261              
262             =cut
263              
264             sub setminus {
265 1316     1316 1 85980 my $X = shift;
266 1316         1713 my $Y = shift;
267 1316         1493 my @X = @{$X};
  1316         3514  
268 1316 100       8163 print STDERR 'setminus# ', @X, ' - ', @{$Y}, ' = ' if $Debug > 1;
  49         98  
269 1316         1382 foreach my $x ( @{$Y} ) {
  1316         2266  
270 1513         8955 @X = grep $x ne $_, @X;
271             }
272 1316 100       3593 print STDERR @X, "\n" if $Debug > 1;
273 1316         4712 return @X;
274             }
275              
276              
277             =item setunion()
278              
279             my @u = setunion(['a','bc',42], [42,4,'a','c']);
280             # ('a','bc',42,4,'c')
281              
282             Given two references to lists, returns the set union of the two lists
283             as a list. Can be imported.
284              
285             =cut
286              
287             sub setunion {
288 585     585 1 2666 my $X = shift;
289 585         590 my $Y = shift;
290 585         655 my @X = @{$X};
  585         1087  
291 585 100       1201 print STDERR 'setunion# ', @X, ' U ', @{$Y}, ' = ' if $Debug > 1;
  23         100  
292 585         1169 foreach my $x ( @{$Y} ) {
  585         1219  
293 385 100       2230 push @X, $x unless grep $x eq $_, @X;
294             }
295 585 100       1268 print STDERR @X, "\n" if $Debug > 1;
296 585         2147 return sort @X;
297             }
298              
299              
300             =item restriction()
301              
302             use Graph::ModularDecomposition;
303             my $G = new Graph::ModularDecomposition;
304             foreach ( 'ac', 'ad', 'bd' ) { $G->add_edge( split // ) }
305             restriction( $G, split(//, 'abdefgh') ); # a-d,b-d
306             $G->restriction( split(//, 'abdefgh') ); # same thing
307              
308             Compute G|X, the subgraph of G induced by X. X is represented as a
309             list of vertices.
310              
311             =cut
312              
313             sub restriction {
314 80     80 1 139 my $G = shift;
315 80 100       217 if ( $Debug > 2 ) { print STDERR 'restriction(', ref($G), ")\n" }
  1         6  
316 80         296 my $h = ($G->copy)->delete_vertices( setminus( [$G->vertices], [@_] ) );
317 80 100       34377 if ( $Debug > 1 ) {
318 1         10 print STDERR 'restriction(', $G, '|', join($QSEP, @_), ') = ', $h, "\n"
319             }
320 80         1570 return $h;
321             }
322              
323              
324             =item factor()
325              
326             $h = factor( $g, [['a','b'], ['c'], ['d','e','f']] );
327             $h = $g->factor( [[qw(a b)], ['c'], [qw(d e f)]] ); # same thing
328              
329             Compute G/P for partition P containing modules. Will fail in odd
330             ways if members of P are not modules.
331              
332             =cut
333              
334             sub factor {
335 44     44 1 64 my $G = shift;
336 44         84 my $P = shift;
337 44         170 my $GP = $G->copy;
338 44         42234 my $p;
339 44         87 foreach my $X ( @{$P} ) {
  44         110  
340 127 100       17876 print STDERR "factor# X = $X\n" if $Debug > 1;
341 127 100       274 print STDERR "factor# \@X = @$X\n" if $Debug > 1;
342 127         143 my $newnode = join $VSEP, @{$X}; # turn nodes a, b, c into new node abc
  127         269  
343 127 100       279 print STDERR "factor# newnode = $newnode\n" if $Debug > 1;
344 127         138 my $a = ${$X}[0];
  127         227  
345 127 100       272 print STDERR "factor# representative node $a\n" if $Debug > 1;
346 127 100       355 if ( $newnode ne $a ) { # do nothing if singleton
347 22         85 $GP->add_vertex( $newnode );
348 22         685 foreach $p ( $GP->predecessors( $a ) ) {
349 20 100       910 print STDERR "factor# predecessor $p\n" if $Debug > 2;
350 20 50       65 $GP = $GP->add_edge( $p, $newnode )
351             unless $GP->has_edge( $p, $newnode );
352             }
353 22         1291 foreach $p ( $GP->successors( $a ) ) {
354 40 100       2283 print STDERR "factor# successor $p\n" if $Debug > 2;
355 40 50       119 $GP = $GP->add_edge( $newnode, $p )
356             unless $GP->has_edge( $newnode, $p );
357             }
358 22         1146 $GP = $GP->delete_vertices( @{$X} );
  22         104  
359             }
360             }
361 44         930 return $GP;
362             }
363              
364              
365             =item partition_subsets()
366              
367             @part = partition_subsets( $G, ['a','b','c'], $w );
368             @part = $G->partition_subsets( ['a','b','c'], $w ); # same thing
369              
370             Partition set of vertices into maximal subsets not distinguished by w in G.
371              
372             =cut
373              
374             sub partition_subsets {
375 477     477 1 2437 my $G = shift;
376 477         536 my $S = shift;
377 477         1739 my $w = shift;
378              
379 477 100       1145 print STDERR 'p..n_subsets# @S = ', @{$S}, ", w = $w \n" if $Debug > 1;
  19         46  
380 477         1609 my (@A, @B, @C, @D);
381 477         517 foreach my $x ( @{$S} ) {
  477         999  
382 789 100       1488 print STDERR 'p..n_subsets# xw = ', $x, $w if $Debug > 2;
383 789 100       3522 if ( $G->has_edge( $w, $x ) ) {
384 183 100       4035 if ( $G->has_edge( $x, $w ) ) { # xw wx (not poset)
385 2         43 push @A, $x;
386 2 100       11 print STDERR ' A = ', @A, "\n" if $Debug > 2;
387             } else { # ~xw wx
388 181         4063 push @B, $x;
389 181 100       5567 print STDERR ' B = ', @B, "\n" if $Debug > 2;
390             }
391             } else {
392 606 100       25327 if ( $G->has_edge( $x, $w ) ) { # xw ~wx
393 178         6438 push @C, $x;
394 178 100       1749 print STDERR ' C = ', @C, "\n" if $Debug > 2;
395             } else { # ~xw ~wx
396 428         12772 push @D, $x;
397 428 100       1346 print STDERR ' D = ', @D, "\n" if $Debug > 2;
398             }
399             }
400             }
401 477         1074 return grep @{$_}, (\@A, \@B, \@C, \@D);
  1908         4723  
402             }
403              
404              
405             =item partition()
406              
407             my $p = partition( $g, $v );
408             $p = $g->partition( $v ); # same thing
409              
410             For a graph, calculate maximal modules not including a given vertex.
411              
412             =cut
413              
414             sub partition {
415 69     69 1 151 my $G = shift;
416 69         121 my $v = shift;
417              
418 69 100       238 print STDERR 'partition# G = ', $G, ", v = $v\n" if $Debug > 1;
419 69         1283 my (%L, @done, $tempset, $S, @ZS, $w);
420 69         244 $S = [ setminus( [ $G->vertices ], [ $v ] ) ];
421 69 100       304 print STDERR 'partition# @S = ', @{$S}, "\n" if $Debug > 1;
  2         6  
422 69         265 $L{$S} = [ $v ];
423 69         145 my @todo = ( $S );
424 69 100       185 print STDERR 'partition# L{S}[0] = ', $L{$S}[0], "\n" if $Debug > 1;
425 69         179 while ( @todo ) {
426 470         725 $S = shift @todo;
427 470         909 @ZS = @{$L{$S}};
  470         28007  
428 470         764 $w = $ZS[0];
429 470 100       2535 print STDERR 'partition# ZS = ', @ZS, "\n" if $Debug > 1;
430 470         2136 delete $L{$S};
431 470         1486 foreach my $W ( $G->partition_subsets( $S, $w ) ) {
432 583 100       2504 print STDERR 'partition# W = ', @{$W}, "\n" if $Debug > 1;
  23         43  
433 583         1128 $tempset = [ setunion( [ setminus( $S, $W ) ],
434             [ setminus( \@ZS, [ $w ] ) ] ) ];
435 583 100       1323 if ( @{$tempset} ) {
  583         1301  
436 401 100       828 print STDERR 'partition# tempset = ', @{$tempset}, "\n"
  17         30  
437             if $Debug > 1;
438 401         1432 $L{$W} = $tempset;
439 401         2551 push @todo, $W;
440             } else {
441 182         2075 push @done, $W;
442             }
443             }
444             }
445 69         391 return \@done;
446             }
447              
448              
449             =item distinguishes()
450              
451             print "yes" if distinguishes( $g, $x, $y, $z );
452             print "yes" if $g->distinguishes( $x, $y, $z ); # same thing
453              
454             True if vertex $x distinguishes vertices $y and $z in graph $g.
455              
456             =cut
457              
458             sub distinguishes {
459 360     360 1 1609 my ($g,$x,$y,$z) = @_;
460 360 100       733 print STDERR " $x$y?", $g->has_edge($x,$y) if $Debug > 1;
461 360 100       3734 print STDERR " $x$z?", $g->has_edge($x,$z) if $Debug > 1;
462 360 100       929 print STDERR " $y$x?", $g->has_edge($y,$x) if $Debug > 1;
463 360 100       951 print STDERR " $z$x?", $g->has_edge($z,$x) if $Debug > 1;
464 360   100     1144 my $ret = ( $g->has_edge($x,$y) != $g->has_edge($x,$z) )
465             || ( $g->has_edge($y,$x) != $g->has_edge($z,$x) );
466 360 100       23548 print STDERR "=$ret\n" if $Debug > 1;
467 360         2918 return $ret;
468             }
469              
470              
471             =item G()
472              
473             $G = G( $g, $v );
474             $G = $g->G( $v ); # same thing
475              
476             "Trivially" calculate G(g,v). dom(G(g,v)) = dom(g)\{v}, and (x,y) is
477             an edge of G(g,v) whenever x distinguishes y and v in g.
478              
479             =cut
480              
481             sub G {
482 52     52 1 89 my $g = shift;
483 52         103 my $v = shift;
484 52         152 my $G = new ref($g);
485 52 100       11500 print STDERR 'G([', $g, "], $v) =...\n" if $Debug;
486 52         1123 X: foreach my $x ( $g->vertices ) {
487 183 100       5561 next X if ( $v eq $x );
488 131 100       325 print STDERR 'X=', $x, "\n" if $Debug > 1;
489 131         1573 $G = $G->add_vertex( $x );
490 131         4352 Y: foreach my $y ( $g->vertices ) {
491 612 100 100     34654 next Y if ( $v eq $y or $x eq $y );
492 350 100       1169 print STDERR 'Y=', $y, "\n" if $Debug > 1;
493 350 100       799 if ( $g->distinguishes( $x, $y, $v ) ) {
494 188 50       475 $G = $G->add_edge( $x, $y ) unless $G->has_edge( $x, $y );
495             }
496             }
497             }
498 52 100       175 print STDERR '...G()=', $G, "\n" if $Debug;
499 52         1330 return $G;
500             }
501              
502              
503             =item tree_to_string()
504              
505             print tree_to_string( $t );
506              
507             String representation of decomposition tree. Returns empty string for
508             an empty decomposition tree. Needs to be explicitly imported. If
509             Graph::vertices returns the vertices in unsorted order, then isomorphic
510             trees can have different string representations.
511              
512             =cut
513              
514             sub tree_to_string {
515 180     180 1 340 my $t = shift;
516 180         243 my $s = '';
517 180 100       433 return $s unless defined $t->{type};
518 174 100       523 $s .= $t->{type} if $t->{type} ne 'leaf';
519 174 100       397 $s .= '_' . $t->{col} if ( $t->{type} eq 'complete' );
520 174         991 $s .= '[' . $t->{value} . ']';
521 174 100       396 if ( $t->{type} ne 'leaf' ) {
522 49         83 my $sep = '';
523 49         65 $s .= '(';
524 49         78 foreach ( @{$t->{children}} ) {
  49         134  
525 131         417 $s .= $sep . tree_to_string( $_ );
526 131         301 $sep = ';';
527             }
528 49         88 $s .= ')';
529             }
530 174         424 return $s;
531             }
532              
533              
534             =item partition_to_string
535              
536             print partition_to_string([['h'], [qw(c a b)], [qw(d e f g)]]);
537             # a+b+c,d+e+f+g,h
538              
539             String representation of partition. Returns empty string for an
540             empty partition. Needs to be explicitly imported.
541              
542             =cut
543              
544             sub partition_to_string {
545 36     36 1 87 return join ',', sort (map { join $QSEP, sort @{$_} } @{+shift});
  125         147  
  125         1902  
  36         69  
546             }
547              
548              
549             =item modular_decomposition_EGMS()
550              
551             use Graph::ModularDecomposition;
552             $g = new Graph::ModularDecomposition;
553             $m = $g->modular_decomposition_EGMS;
554              
555             Compute modular decomposition tree of the input, which must be
556             a Graph::ModularDecomposition object, using algorithm 6.1 of
557             A. Ehrenfeucht, H. N. Gabow, R. M. McConnell, S. J. Sullivan, "An
558             O(n^2) Divide-and-Conquer Algorithm for the Prime Tree Decomposition
559             of Two-Structures and Modular Decomposition of Graphs", Journal of
560             Algorithms 16 (1994), pp. 283-294.
561              
562             The decomposition tree consists of nodes with attributes: 'type' is
563             a string matching /^leaf|primitive|complete|linear$/, 'children' is
564             a reference to a potentially empty list of pointers to other nodes,
565             'value' is a string with the vertices in the decomposition defined
566             by the tree, separated by '|' (VSEP), and 'col' is a string containing the
567             colour of the module, matching /^0|1|01$/. A node with 'type' of
568             'complete' is parallel if 'col' is '0' and series if 'col' is '1'.
569             A node with 'type' of 'linear' has 'col' of '01'. Use the function
570             tree_to_string() to convert the tree into a more generally usable form.
571              
572             =cut
573              
574             sub modular_decomposition_EGMS {
575 114     114 1 737 my $g = shift;
576 114         170 my $md = 0;
577 114         173 $md ++;
578 114         278 my $B = ' 'x$md;
579 114 100       612 print STDERR $B, 'MD(', $g, ")=...\n" if $Debug;
580 114         2516 my $v = ($g->vertices)[0];
581 114 100       4742 print STDERR $B, 'v=', (defined($v) ? $v : 'undef'), "\n" if $Debug;
    100          
582              
583 114         217 my $t = {};
584 114 100       305 unless ( $v ) {
585 3 100       16 print STDERR $B, '...MD=', tree_to_string( $t ), "\n" if $Debug;
586 3         7 $md --;
587 3         12 return $t;
588             }
589 111         315 $t->{type} = 'leaf';
590 111         371 $t->{children} = [];
591 111 100       503 if ($g->canonical_form()) {
592 7         20 $t->{value} = join($VSEP, sort($g->vertices));
593             } else {
594 104         308 $t->{value} = join($VSEP, $g->vertices);
595             }
596 111         4396 $t->{col} = '0';
597              
598 111 100       291 if ( scalar $g->vertices == 1 ) {
599 70 100       2478 print STDERR $B, '...MD=', tree_to_string( $t ), "\n" if $Debug;
600 70         87 $md --;
601 70         166 return $t;
602             }
603              
604 41         2208 my $p = partition( $g, $v );
605 41         74 push @{$p}, [ $v ];
  41         96  
606 41         148 my $gd = $g->factor( $p );
607 41 100       149 print STDERR $B, 'gd = ', $gd, "\n" if $Debug;
608 41         1154 my $Gdd = $gd->G($v)->strongly_connected_graph;
609 41 100       135317 print STDERR $B, 'Gdd = [', $Gdd, '], ', scalar $Gdd->vertices, "\n" if $Debug;
610              
611 41         688 my $u = $t;
612 41         65 my @f;
613 41         200 while ( @f = grep( $Gdd->out_degree($_) == 0 , $Gdd->vertices ) ) {
614 53 100       20829 print STDERR $B, "\@f=[@f]\n" if $Debug;
615 53         85 my @s;
616 53         198 foreach my $s ( $Gdd->vertices ) {
617 75         2752 push @s, split(/$PSEP/, $s);
618             }
619 53 100       226 if ($g->canonical_form()) {
620 3         21 $u->{value} = join('', sort($v, @s));
621             } else {
622 50         200 $u->{value} = join('', ($v, @s));
623             }
624 53         165 my $w = {};
625 53         451 $w->{type} = 'leaf';
626 53         184 $w->{children} = [];
627 53         180 $w->{value} = $v;
628 53         121 $w->{col} = '0';
629 53         76 push @{$u->{children}}, $w;
  53         430  
630              
631 53         216 $Gdd->delete_vertices( @f );
632 53         9118 my @F;
633 53         124 foreach my $f ( @f ) {
634 55         264 foreach my $F ( split /$PSEP/, $f ) {
635 77 50       412 push @F, $F unless grep $F eq $_, @F;
636             }
637             }
638 53 100       173 print STDERR $B, "\@F=@F\n" if $Debug;
639 53 100 100     346 if ( @f == 1 and @F > 1 ) {
640 11         36 $u->{type} = 'primitive';
641 11         27 $u->{col} = '0';
642             } else {
643 42         155 my $x = substr $F[0], 0, 1; # single-char vertex names!
644 42 100       174 if ( $g->has_edge($v, $x) == $g->has_edge($x, $v) ) {
645 10         405 $u->{type} = 'complete'; # 0 parallel, 1 series
646 10 50       38 $u->{col} = $g->has_edge($v, $x) ? '1' : '0';
647             } else {
648 32         1610 $u->{type} = 'linear';
649 32         82 $u->{col} = '01';
650             }
651             }
652 53 100       461 print STDERR $B, 'u = ', tree_to_string( $u ), "\n" if $Debug;
653 53         252 foreach my $X ( @F ) {
654 77         503 my $m = $g->restriction( split /$WSEP/, $X )
655             ->modular_decomposition_EGMS;
656 77 100 66     1333 if ( defined $m->{col}
      33        
      66        
657             and ( $u->{col} eq $m->{col} )
658             and (
659             ( $u->{type} eq 'complete' and $m->{type} eq 'complete' )
660             or ( $u->{type} eq 'linear' and $m->{type} eq 'linear' )
661             )
662             ) {
663 8 50       34 if ( $Debug ) {
664 0         0 print STDERR $B, "u->children= @{$u->{children}}\n";
  0         0  
665 0         0 print STDERR $B, 'm->children= ';
666 0         0 my $sep = '';
667 0         0 foreach ( @{$m->{children}} ) {
  0         0  
668 0         0 print STDERR $sep, '[', tree_to_string( $_ ), ']';
669 0         0 $sep = ', ';
670             }
671 0         0 print STDERR "\n";
672             }
673 8         15 push @{$u->{children}}, @{$m->{children}};
  8         18  
  8         39  
674             } else {
675 69         92 push @{$u->{children}}, $m;
  69         235  
676             }
677             }
678 53         260 $u = $w;
679             }
680 41 100       1508 print STDERR $B, '...MD=', tree_to_string( $t ), "\n" if $Debug;
681 41         52 $md --;
682 41         793 return $t;
683             }
684              
685              
686             =item classify()
687              
688             use Graph::ModularDecomposition;
689             my $g = new Graph::ModularDecomposition;
690             my $c = classify( $g );
691             $c = $g->classify; # same thing
692              
693             Based on the modular decomposition tree, returns:
694             n non-transitive
695             i indecomposable
696             d decomposable but not SP, at least one non-primitive node
697             s series-parallel
698             p decomposable but each module is primitive or series
699             u unclassified: should not happen
700              
701             =cut
702              
703             sub classify {
704 36     36 1 39885 my $g = shift;
705 36 100       126 return 'n' unless $g->check_transitive;
706 33         26813 my $s = tree_to_string( $g->modular_decomposition_EGMS );
707 33 100       379 return 'i' if $s =~ m/^primitive\[[^\]]+\]\([^\(]*$/;
708 26 100 100     153 return 'd' if $s =~ m/primitive/ and $s =~ m/complete_|linear/;
709 25 100       378 return 's' if $s !~ m/primitive|complete_1/; # matches empty string
710 1 50       14 return 'p' if $s =~ m/primitive|complete_1/;
711 0           return 'u';
712             }
713              
714              
715             =item to_bitvector2()
716              
717             $b = $g->to_bitvector2;
718              
719             Convert input graph to Bitvector2 output.
720             L version 20104 permits
721             multi-edges; these will be collapsed into a single edge in the
722             output Bitvector2. The Bitvector2 is relative to the unique
723             lexicographic ordering of the vertices. This method is only present
724             if L is found.
725              
726             =cut
727              
728             eval {require Graph::Bitvector2; 1} and # alas, circular dependency here
729             eval q{
730             sub to_bitvector2 {
731             my $g = shift;
732             my @v = sort $g->vertices;
733             my @bits;
734             while ( @v ) {
735             my $x = shift @v;
736             foreach my $y ( @v ) {
737             push @bits, (
738             $g->has_edge( $x, $y )
739             ? 1
740             : ( $g->has_edge( $y, $x ) ? 2 : 0 )
741             );
742             }
743             }
744             return new Graph::Bitvector2 (join '', @bits);
745             }
746             };
747              
748              
749             =back
750              
751             =cut
752              
753             1;
754             __END__