File Coverage

blib/lib/Graph/TransitiveClosure/Matrix.pm
Criterion Covered Total %
statement 142 142 100.0
branch 81 90 90.0
condition 29 32 90.6
subroutine 16 16 100.0
pod 9 9 100.0
total 277 289 95.8


line stmt bran cond sub pod time code
1             package Graph::TransitiveClosure::Matrix;
2              
3 6     6   48 use strict;
  6         19  
  6         187  
4 6     6   38 use warnings;
  6         13  
  6         141  
5              
6 6     6   2982 use Graph::AdjacencyMatrix;
  6         15  
  6         172  
7 6     6   38 use Graph::Matrix;
  6         19  
  6         115  
8 6     6   28 use Scalar::Util qw(weaken);
  6         13  
  6         393  
9 6     6   42 use List::Util qw(min);
  6         11  
  6         9783  
10              
11             sub _A() { 0 } # adjacency
12             sub _D() { 1 } # distance
13             sub _S() { 2 } # successors
14             sub _V() { 3 } # vertices
15             sub _G() { 4 } # the original graph (OG)
16              
17             sub _new {
18 70     70   199 my ($g, $class, $am_opt, $want_transitive, $want_reflexive, $want_path, $want_path_vertices, $want_path_count) = @_;
19 70         354 my $m = Graph::AdjacencyMatrix->new($g, %$am_opt);
20 70         235 my @V = $g->vertices;
21 70         146 my %v2i; @v2i{ @V } = 0..$#V; # paths are in array -> stable ordering
  70         363  
22 70         258 my $am = $m->adjacency_matrix;
23 70         221 $am->[1] = \%v2i;
24 70         211 my ($dm, @di); # The distance matrix.
25 70         0 my ($sm, @si); # The successor matrix.
26             # directly use (not via API) arrays of bit-vectors etc for speed.
27             # the API is so low-level it adds no clarity anyway
28 70         95 my @ai = @{ $am->[0] };
  70         241  
29 70         180 my $multi = $g->multiedged;
30 70 100       203 unless ($want_transitive) {
31 60   66     177 $dm = $m->distance_matrix || Graph::Matrix->new($g); # if no distance_matrix in AM, we make our own
32 60 100       140 if ($want_path_count) {
33             # force defined
34 1         19 @di = map [ (0) x @V ], 0..$#V;
35             } else {
36 59         78 @di = @{ $dm->[0] };
  59         511  
37             }
38 60         205 $sm = Graph::Matrix->new($g);
39 60         218 $dm->[1] = $sm->[1] = \%v2i;
40 60         95 @si = @{ $sm->[0] };
  60         151  
41 60         200 for (my $iu = $#V; $iu >= 0; $iu--) {
42 437 100       1205 vec($ai[$iu], $iu, 1) = 1 if $want_reflexive;
43 437         1010 for (my $iv = $#V; $iv >= 0; $iv--) {
44 17623 100       36332 next unless vec($ai[$iu], $iv, 1);
45 1166 100 100     3999 if ($want_path_count or !defined $di[$iu][$iv]) {
    100 100        
46 231 100       418 $di[$iu][$iv] = $iu == $iv ? 0 : 1;
47             } elsif ($multi and ref($di[$iu][$iv]) eq 'HASH') {
48 4         7 $di[$iu][$iv] = min values %{ $di[$iu][$iv] };
  4         21  
49             }
50 1166 100       3302 $si[$iu]->[$iv] = $V[$iv] unless $iu == $iv;
51             }
52             }
53             }
54             # naming here is u = start, v = midpoint, w = endpoint
55 70         284 for (my $iv = $#V; $iv >= 0; $iv--) {
56 466         747 my $div = $di[$iv];
57 466         656 my $aiv = $ai[$iv];
58 466         998 for (my $iu = $#V; $iu >= 0; $iu--) {
59 17727         23629 my $aiu = $ai[$iu];
60 17727 100       36506 next if !vec($aiu, $iv, 1);
61 2000 100       3326 if ($want_transitive) {
62 55         128 for (my $iw = $#V; $iw >= 0; $iw--) {
63 195 100 100     709 return 0
      100        
64             if $iw != $iv &&
65             vec($aiv, $iw, 1) &&
66             !vec($aiu, $iw, 1);
67             }
68 51         104 next;
69             }
70 1945         2538 my $aiuo = $aiu;
71 1945         2640 $aiu |= $aiv;
72 1945 100       3400 if ($aiu ne $aiuo) {
73 994         1348 $ai[$iu] = $aiu;
74 994 50       1771 $aiv = $aiu if $iv == $iu;
75             }
76 1945 100       3201 next if !$want_path;
77 1925         2552 my $diu = $di[$iu];
78 1925         2908 my $d1a = $diu->[$iv];
79 1925         3892 for (my $iw = $#V; $iw >= 0; $iw--) {
80 94004 100       183950 next unless vec($aiv, $iw, 1);
81 28011 100       43123 if ($want_path_count) {
82 13 100 100     36 $diu->[$iw]++ if $iu != $iv and $iv != $iw and $iw != $iu;
      66        
83 13         24 next;
84             }
85 27998         37099 my $d0 = $diu->[$iw];
86 27998         34698 my $d1b = $div->[$iw];
87 27998         36226 my $d1 = $d1a + $d1b;
88 27998 100 100     76271 if (!defined $d0 || ($d1 < $d0)) {
89             # print "d1 = $d1a ($V[$iu], $V[$iv]) + $d1b ($V[$iv], $V[$iw]) = $d1 ($V[$iu], $V[$iw]) (".(defined$d0?$d0:"-").") (propagate=".($aiu ne $aiuo?1:0).")\n";
90 7141         9924 $diu->[$iw] = $d1;
91 7141 100       19555 $si[$iu]->[$iw] = $si[$iu]->[$iv]
92             if $want_path_vertices;
93             }
94             }
95             }
96             }
97 66 100       227 return 1 if $want_transitive;
98 60         87 my %V; @V{ @V } = @V;
  60         452  
99 60         233 $am->[0] = \@ai;
100 60 50       204 $dm->[0] = \@di if defined $dm;
101 60 50       168 $sm->[0] = \@si if defined $sm;
102 60         263 weaken(my $og = $g);
103 60         742 bless [ $am, $dm, $sm, \%V, $og ], $class;
104             }
105              
106             sub new {
107 70     70 1 233 my ($class, $g, %opt) = @_;
108 70         199 my %am_opt = (distance_matrix => 1);
109             $am_opt{attribute_name} = delete $opt{attribute_name}
110 70 100       181 if exists $opt{attribute_name};
111             $am_opt{distance_matrix} = delete $opt{distance_matrix}
112 70 50       171 if $opt{distance_matrix};
113             $opt{path_length} = $opt{path_vertices} = delete $opt{path}
114 70 100       194 if exists $opt{path};
115 70         130 my $want_path_length = delete $opt{path_length};
116 70         107 my $want_path_count = delete $opt{path_count};
117 70         103 my $want_path_vertices = delete $opt{path_vertices};
118 70         123 my $want_reflexive = delete $opt{reflexive};
119             $am_opt{is_transitive} = my $want_transitive = delete $opt{is_transitive}
120 70 100       158 if exists $opt{is_transitive};
121 70         236 Graph::_opt_unknown(\%opt);
122 70 100       182 $want_reflexive = 1 unless defined $want_reflexive;
123 70   66     297 my $want_path = $want_path_length || $want_path_vertices || $want_path_count;
124             # $g->expect_dag if $want_path;
125 70 100       133 $am_opt{distance_matrix} = 0 if $want_path_count;
126 70         175 _new($g, $class,
127             \%am_opt,
128             $want_transitive, $want_reflexive,
129             $want_path, $want_path_vertices, $want_path_count);
130             }
131              
132             sub has_vertices {
133 17892     17892 1 24505 my $tc = shift;
134 17892         28711 for my $v (@_) {
135 35778 100       74388 return 0 unless exists $tc->[ _V ]->{ $v };
136             }
137 17883         32816 return 1;
138             }
139              
140             sub is_reachable {
141 14508     14508 1 26399 my ($tc, $u, $v) = @_;
142 14508 100       23941 return undef unless $tc->has_vertices($u, $v);
143 14502 100       27905 return 1 if $u eq $v;
144 13636         28687 $tc->[ _A ]->get($u, $v);
145             }
146              
147             sub is_transitive {
148 16 100   16 1 64 return __PACKAGE__->new($_[0], is_transitive => 1) if @_ == 1; # Any graph
149             # A TC graph
150 6         15 my ($tc, $u, $v) = @_;
151 6 50       17 return undef unless $tc->has_vertices($u, $v);
152 6         22 $tc->[ _A ]->get($u, $v);
153             }
154              
155             sub vertices {
156 603     603 1 869 my $tc = shift;
157 603         800 values %{ $tc->[3] };
  603         4217  
158             }
159              
160             sub path_length {
161 3007     3007 1 5178 my ($tc, $u, $v) = @_;
162 3007 100       5094 return undef unless $tc->has_vertices($u, $v);
163 3004 100       6037 return 0 if $u eq $v;
164 2748         5955 $tc->[ _D ]->get($u, $v);
165             }
166              
167             sub path_successor {
168 380     380 1 647 my ($tc, $u, $v) = @_;
169 380 100       788 return undef if $u eq $v;
170 371 50       651 return undef unless $tc->has_vertices($u, $v);
171 371         979 $tc->[ _S ]->get($u, $v);
172             }
173              
174             sub path_vertices {
175 267     267 1 581 my ($tc, $u, $v) = @_;
176 267 100       589 return unless $tc->is_reachable($u, $v);
177 252 100       838 return wantarray ? () : 0 if $u eq $v;
    100          
178 207         442 my @v = ( $u );
179 207         443 while ($u ne $v) {
180 353 50       735 last unless defined($u = $tc->path_successor($u, $v));
181 353         886 push @v, $u;
182             }
183 207 50       1023 $tc->[ _S ]->set($u, $v, [ @v ]) if @v;
184 207         1107 return @v;
185             }
186              
187             sub all_paths {
188 29     29 1 70 my ($tc, $u, $v, $seen) = @_;
189 29 50       70 return if $u eq $v;
190 29   100     113 $seen ||= {};
191 29 100       65 return if exists $seen->{$u};
192 28         106 $seen = { %$seen, $u => undef }; # accumulate, but don't mutate
193 28         45 my @found;
194 28 100       147 push @found, [$u, $v] if $tc->[ _G ]->has_edge($u, $v);
195 28   100     110 push @found,
196             map [$u, @$_],
197             map $tc->all_paths($_, $v, $seen),
198             grep $tc->is_reachable($_, $v),
199             grep $_ ne $v && $_ ne $u, $tc->[ _G ]->successors($u);
200 28         146 @found;
201             }
202              
203             1;
204             __END__