File Coverage

blib/lib/Boost/Graph.pm
Criterion Covered Total %
statement 203 236 86.0
branch 65 102 63.7
condition 29 63 46.0
subroutine 26 30 86.6
pod 21 23 91.3
total 344 454 75.7


line stmt bran cond sub pod time code
1             package Boost::Graph;
2              
3             # Dev release version numbering ends in a _xxx suffix, and is evaled to a number.
4             # See perldoc perlmodstyle for explanation.
5             our $VERSION = '1.4_001';
6             our $XS_VERSION = $VERSION;
7             $VERSION = eval $VERSION;
8              
9             #####################################################################################
10             # Graph.pm
11             # David Burdick, 11/08/2004
12             #
13             # The main module for the Perl Boost interface
14             #####################################################################################
15 1     1   29087 use strict;
  1         3  
  1         34  
16 1     1   398 use Boost::Graph::Directed;
  1         3  
  1         78  
17 1     1   533 use Boost::Graph::Undirected;
  1         3  
  1         3214  
18              
19             #______________________________________________________________________________________________________________
20             ### Variables
21             #
22             # net_id - unique identifier for network
23             # net_name - name of the network
24             # _edges - a hash of hashes. First key is first node, second key is second node.
25             # _nodes - a hash where keys are the the node objects, value is the node_id
26             # _nodes_lookup - a hash where keys are the unique id for nodes, value is actual object
27             # _nodecount - the number of nodes in the network
28             # _edgecount - the number of edges in the network
29             # _node_neighbors - hash on node id, stores a hash whose keys are node ids of its neighbors
30             #
31             #______________________________________________________________________________________________________________
32             ### ALGORITHMS
33             ## C++
34             # breadth_first_search($start_node)
35             # depth_first_search($start_node)
36             #
37             ## Perl
38             # transitive_links($nodes) - receives a listref of nodes and returns a listref of nodes that are (disjoint
39             # from the input set) transitive connectors of the input set in the current network.
40             # The transitive distance is limited to one node. (i.e. given a and c as input, and
41             # with edges a-b and b-c, then node b will be returned)
42             #______________________________________________________________________________________________________________
43             sub new {
44 17     17 1 14224 my $this = shift;
45 17         41 my %args = @_;
46 17   33     75 my $class = ref($this) || $this;
47 17         29 my $self = {};
48 17         40 $self->{_nodecount} = 0;
49 17         35 $self->{_edgecount} = 0;
50 17 100       38 if($args{'directed'}) { # connect to C++ libraries
51 4         9 $self->{_directed} = 1;
52 4         116 $self->{_bgi} = new Boost::Graph::Directed;
53             } else {
54 13         18 $self->{_directed} = 0;
55 13         183 $self->{_bgi} = new Boost::Graph::Undirected;
56             }
57 17 50       43 $self->{net_name} = $args{net_name} if $args{net_name};
58 17 50       34 $self->{net_id} = $args{net_id} if $args{net_id};
59            
60 17         30 bless $self, $class;
61 17         74 return($self);
62             }
63             #______________________________________________________________________________________________________________
64             sub add_edge {
65 90     90 1 950 my ($self, %args) = @_;
66 90         91 my ($node1, $node2);
67             # check for simple edge add
68 90 100       149 if(@_ == 3) {
69 7         8 $node1=$_[1];
70 7         9 $node2=$_[2];
71             } else {
72 83 50 33     551 return unless $args{node1} && $args{node2};
73 83         92 $node1=$args{node1};
74 83         95 $node2=$args{node2};
75             }
76            
77              
78 90         101 my $weight = $args{weight};
79 90         90 my $edge_obj = $args{edge};
80 90 100       153 $weight or $weight=1.0;
81 90 100       144 $edge_obj or $edge_obj=1;
82              
83             # add nodes/get node_id
84 90         157 my $node1_id = $self->_get_node_id($node1);
85 90         162 my $node2_id = $self->_get_node_id($node2);
86 90 50 33     572 return undef if $node1_id==0 || $node2_id==0; # problem!
87             # check for duplicate edge
88 90 100       159 return 0 if $self->has_edge($node1,$node2);
89              
90             # add neighbors
91 88         199 $self->{_node_neighbors}->{$node1_id}->{$node2_id} = 1;
92 88         150 $self->{_node_neighbors}->{$node2_id}->{$node1_id} = 1;
93             # add parents
94 88         149 $self->{_node_parents}->{$node2_id}->{$node1_id} = 1;
95             # store edge and edge_object
96 88 100 100     211 if($node1_id < $node2_id || $self->{_directed}) {
97 78         151 $self->{_edges}->{$node1_id}->{$node2_id} = $edge_obj;
98             } else {
99 10         20 $self->{_edges}->{$node2_id}->{$node1_id} = $edge_obj;
100             }
101 88         99 $self->{_edgecount}++;
102 88         728 $self->{_bgi}->_addEdge($node1_id,$node2_id,$weight); # C++
103 88         342 return 1;
104             }
105             #______________________________________________________________________________________________________________
106             sub add_node {
107 2     2 1 846 my ($self, $node) = @_;
108 2         4 my $isnew = $self->{_nodecount}+1;
109 2         5 my $node_id = $self->_get_node_id($node);
110 2 100       6 if($isnew == $node_id) {
111 1         82 $self->{_bgi}->_addNode($node_id); # C++
112 1         3 return 1;
113             } else {
114 1         3 return 0;
115             }
116             }
117             #______________________________________________________________________________________________________________
118             sub get_edge {
119 0     0 0 0 my ($self,$source,$sink) = @_;
120 0         0 my @edges;
121 0         0 my $source_id = $self->_get_node_id($source);
122 0         0 my $sink_id = $self->_get_node_id($sink);
123 0         0 my $a = $self->{_nodes_lookup}->{$source_id};
124 0         0 my $b = $self->{_nodes_lookup}->{$sink_id};
125 0         0 return [$a, $b, $self->{_edges}->{$source_id}->{$sink_id}];
126             }
127             #______________________________________________________________________________________________________________
128             sub get_edges {
129 1     1 1 947 my ($self) = @_;
130 1         1 my @edges;
131 1         2 foreach my $source (keys %{$self->{_edges}}) {
  1         5  
132 2         3 foreach my $sink (keys %{$self->{_edges}->{$source}}) {
  2         6  
133 3         7 my $a = $self->{_nodes_lookup}->{$source};
134 3         4 my $b = $self->{_nodes_lookup}->{$sink};
135 3         12 push @edges, [$a, $b, $self->{_edges}->{$source}->{$sink}];
136             }
137             }
138 1         4 return \@edges;
139             }
140             #______________________________________________________________________________________________________________
141             sub get_nodes {
142 1     1 1 4 my ($self) = @_;
143 1         2 my @nodes = values %{$self->{_nodes_lookup}};
  1         4  
144 1         3 return \@nodes;
145             }
146             #______________________________________________________________________________________________________________
147             sub has_edge {
148 99     99 1 125 my ($self,$node1,$node2) = @_;
149 99 50 33     148 if($self->has_node($node1) && $self->has_node($node2)) {
150 99         157 my $node1_id = $self->_get_node_id($node1);
151 99         174 my $node2_id = $self->_get_node_id($node2);
152 99 50 33     361 return undef if $node1_id==0 || $node2_id==0; # problem!
153             # check for duplicate edge being careful not to make empty hashes on the first id. don't check reverse for directed graphs
154 99 100 100     352 if ($self->{_edges}->{$node1_id}) {
    100          
155 42 100       130 return 1 if $self->{_edges}->{$node1_id}->{$node2_id};
156             } elsif ($self->{_edges}->{$node2_id} && !$self->{_directed}) {
157 5 50       12 return 1 if $self->{_edges}->{$node2_id}->{$node1_id};
158             }
159             }
160 89         410 return undef;
161             }
162             #______________________________________________________________________________________________________________
163             sub has_node {
164 236     236 1 283 my ($self,$node,$id_name) = @_;
165 236 50       393 return undef unless $node;
166 236 50       304 if($id_name) {
167 0         0 foreach my $n (values %{$self->{_nodes_lookup}}) {
  0         0  
168 0 0       0 return 1 if $n->{$id_name} eq $node->{$id_name};
169             }
170             } else {
171 236 50       1321 return 1 if $self->{_nodes}->{$node};
172             }
173 0         0 return undef;
174             }
175             #______________________________________________________________________________________________________________
176             sub neighbors {
177 1     1 1 5 my ($self,$root) = @_;
178 1         4 my $ids = $self->_neighbors($root);
179 1         2 my @nodes;
180 1         2 foreach my $nid (@$ids) {
181 2         5 push @nodes, $self->{_nodes_lookup}->{$nid};
182             }
183 1         3 return \@nodes;
184             }
185             #______________________________________________________________________________________________________________
186             sub children_of_directed {
187 10     10 1 789 my ($self,$source) = @_;
188 10 50       25 die "children_of_directed(...) only for directed graphs." unless $self->{_directed};
189 10 50       17 return [] unless $self->has_node($source);
190 10         22 my $nid = $self->_get_node_id($source);
191             # retrieve ids of children and return objects
192 10 100       28 if($self->{_edges}->{$nid}) {
193 6         7 my @nodeids = keys %{ $self->{_edges}->{$nid} };
  6         33  
194 6         8 my @node_objs;
195 6         10 foreach my $id (@nodeids) {
196 10         26 push @node_objs, $self->{_nodes_lookup}->{$id};
197             }
198 6         20 return \@node_objs;
199             }
200 4         19 return [];
201             }
202             #______________________________________________________________________________________________________________
203             sub parents_of_directed {
204 0     0 1 0 my ($self,$source) = @_;
205 0 0       0 die "parents_of_directed(...) only for directed graphs." unless $self->{_directed};
206 0 0       0 return [] unless $self->has_node($source);
207 0         0 my $nid = $self->_get_node_id($source);
208             # retrieve ids of parents and return objects
209 0 0       0 if($self->{_node_parents}->{$nid}) {
210 0         0 my @nodeids = keys %{ $self->{_node_parents}->{$nid} };
  0         0  
211 0         0 my @node_objs;
212 0         0 foreach my $id (@nodeids) {
213 0         0 push @node_objs, $self->{_nodes_lookup}->{$id};
214             }
215 0         0 return \@node_objs;
216             }
217 0         0 return [];
218             }
219             #______________________________________________________________________________________________________________
220             sub nodecount {
221 0     0 1 0 my ($self) = @_;
222 0         0 return $self->{_nodecount};
223             }
224             #______________________________________________________________________________________________________________
225             sub edgecount {
226 0     0 1 0 my ($self) = @_;
227 0         0 return $self->{_edgecount};
228             }
229             #______________________________________________________________________________________________________________
230             sub add_path {
231 2     2 1 33 my ($self,@path) = @_;
232 2         8 for(my $i=0; $i<@path; $i++) {
233 8 100       19 last if ($i+1)>=@path;
234 6         17 $self->add_edge(node1=>$path[$i],node2=>$path[$i+1]);
235             }
236 2         5 return 1;
237             }
238             #______________________________________________________________________________________________________________
239             sub has_path {
240 3     3 1 12 my ($self,@path) = @_;
241 3         12 for(my $i=0; $i<@path; $i++) {
242 8 100       18 last if ($i+1)>=@path;
243 6         14 my $has = $self->has_edge($path[$i],$path[$i+1]);
244 6 100       22 return 0 if !$has;
245             }
246 2         10 return 1;
247             }
248             #______________________________________________________________________________________________________________
249             ### Private methods
250             # returns a listref of node ids for the neighbors of the node
251             sub _neighbors {
252 7     7   10 my ($self,$root) = @_;
253 7 50       13 if($self->has_node($root)) {
254 7         9 my @result = keys %{ $self->{_node_neighbors}->{$self->_get_node_id($root)} };
  7         14  
255 7         18 return \@result;
256             }
257 0         0 return undef;
258             }
259             #______________________________________________________________________________________________________________
260             # returns node's unique id. If node doesn't exist, it is added
261             sub _get_node_id {
262 423     423   1164 my ($self, $node) = @_;
263 423         349 my $node_id;
264 423 50       655 return undef unless $node;
265 423 100       918 if($self->{_nodes}->{$node}) {
266 336         608 $node_id = $self->{_nodes}->{$node};
267             } else {
268 87         133 $node_id = ++$self->{_nodecount};
269 87         188 $self->{_nodes}->{$node} = $node_id;
270 87         172 $self->{_nodes_lookup}->{$node_id} = $node;
271             }
272 423         731 return $node_id;
273             }
274             #______________________________________________________________________________________________________________
275             # takes a listref of node_ids and returns a listref of the actual objects
276             sub _get_node_list {
277 7     7   13 my ($self,$node_order) = @_;
278 7 50       18 return undef unless $node_order;
279 7         48 my @traversed_nodes;
280 7         19 foreach my $nid (@$node_order) {
281 53 100       292 push @traversed_nodes, $self->{_nodes_lookup}->{$nid} if $self->{_nodes_lookup}->{$nid};
282             }
283 7         173 return \@traversed_nodes;
284             }
285              
286              
287              
288             #______________________________________________________________________________________________________________
289             ### PERL ALGORITHMS
290             # transitive_links($nodes) - receives a listref of nodes and returns a listref of nodes that are (disjoint
291             # from the input set) transitive connectors of the input set in the current network.
292             # The transitive distance is limited to one node. (i.e. given a and c as input, and
293             # with edges a-b and b-c, then node b will be returned)
294             sub transitive_links {
295 1     1 1 8 my ($self,$roots) = @_;
296 1 50       4 return undef unless $roots;
297 1         1 my %rootids; # keys are id's for input nodes
298             my %hotspots; # keys are id's for hotspot nodes in the graph, values are the nodes
299            
300             # get id's for each node that's in the graph (none added)
301 1         3 foreach my $node (@$roots) {
302 3 50       7 $rootids{$self->_get_node_id($node)} = 1 if $self->has_node($node);
303             }
304             # find transitive nodes for each input node
305 1         5 foreach my $nid (keys %rootids) {
306 3         8 my $nbors = $self->_neighbors($self->{_nodes_lookup}->{$nid});
307 3         5 foreach my $nbor_id (@$nbors) {
308 7 100 100     31 next if $hotspots{$nbor_id} || $rootids{$nbor_id}; # skip node if it's a hotspot already or in the input list
309 3         9 my $oneoff_nbors = $self->_neighbors($self->{_nodes_lookup}->{$nbor_id});
310              
311             # this node is a hotspot if the neighbors contain a node in the input list that is not the start node
312             # my $num_oons = scalar @{$oneoff_nbors};
313 3         6 foreach my $oneoff_nbors_id (@$oneoff_nbors) {
314 8 100       19 next if $hotspots{$nbor_id};
315             # my $oneoff_nbors_id = $oneoff_nbors->[$i];
316 7 100 100     43 if ($rootids{$oneoff_nbors_id} && $oneoff_nbors_id != $nid) {
317 2         9 $hotspots{$nbor_id} = $self->{_nodes_lookup}->{$nbor_id};
318             }
319             }
320             }
321             }
322 1         9 my @retlist = values %hotspots;
323 1         5 return \@retlist;
324             }
325             #______________________________________________________________________________________________________________
326             # Depth First Search with node level information
327             sub depth_first_search_levels {
328 1     1 0 8415 my ($self,$node) = @_;
329 1 50 33     4 return unless $self->has_node($node) && $self->{_directed};
330 1         2 my @ret;
331 1         5 $self->_depth_first_search_levels(\@ret,$node,0);
332 1         3 return \@ret;
333             }
334             sub _depth_first_search_levels {
335 8     8   11 my ($self,$ret,$node,$depth) = @_;
336 8         13 my %tmp;
337 8         18 $tmp{node} = $node;
338 8         14 $tmp{depth} = $depth;
339 8         12 push @$ret,\%tmp;
340 8         10 foreach my $child (@{$self->children_of_directed($node)}) {
  8         16  
341 7         35 $self->_depth_first_search_levels($ret,$child,$depth+1);
342             }
343             }
344             #______________________________________________________________________________________________________________
345              
346              
347              
348              
349              
350             #______________________________________________________________________________________________________________
351             ### C++ ALGORITHMS
352             # Breadth First Search
353             sub breadth_first_search {
354 3     3 1 17 my ($self,$start_node) = @_;
355 3 50 33     13 return undef unless $start_node && $self->has_node($start_node);
356            
357 3         8 my $start_node_id = $self->_get_node_id($start_node);
358 3 50       7 return undef unless $start_node_id;
359 3         621 my @node_order = $self->{_bgi}->breadthFirstSearch($start_node_id);
360 3         17 return $self->_get_node_list(\@node_order);
361             }
362             #______________________________________________________________________________________________________________
363             # Depth First Search
364             sub depth_first_search {
365 2     2 1 5235 my ($self,$start_node) = @_;
366 2 50 33     14 return undef unless $start_node && $self->has_node($start_node);
367            
368 2         7 my $start_node_id = $self->_get_node_id($start_node);
369 2 50       53 return undef unless $start_node_id;
370 2         210 my @node_order = $self->{_bgi}->depthFirstSearch($start_node_id);
371 2         9 return $self->_get_node_list(\@node_order);
372             }
373             #______________________________________________________________________________________________________________
374             ### Shortest Paths Algorithms ###
375             #______________________________________________________________________________________________________________
376             # Dijkstra's Shortest Paths
377             # returns hashref: {path|weight}. path is a listref, weight is a scalar
378             sub dijkstra_shortest_path {
379 2     2 1 9 my ($self,$start_node,$end_node) = @_;
380 2 50 33     10 return undef unless $start_node && $self->has_node($start_node) && $end_node && $self->has_node($end_node);
      33        
      33        
381            
382 2         3 my %ret;
383 2         5 my $start_id = $self->_get_node_id($start_node);
384 2         4 my $end_id = $self->_get_node_id($end_node);
385 2         790 my ($path_wt,@node_order) = $self->{_bgi}->dijkstraShortestPath($start_id,$end_id);
386 2         7 $ret{weight}=$path_wt;
387 2         7 $ret{path}=$self->_get_node_list(\@node_order);
388              
389 2         10 return \%ret;
390             }
391             #______________________________________________________________________________________________________________
392             # Johnsons All Pairs Shortest Paths
393             # returns path weight.
394             sub all_pairs_shortest_paths_johnson {
395 3     3 1 14 my ($self,$start_node,$end_node) = @_;
396 3 50 33     11 return undef unless $start_node && $self->has_node($start_node) && $end_node && $self->has_node($end_node);
      33        
      33        
397            
398 3         3 my $ret;
399 3         5 my $start_id = $self->_get_node_id($start_node);
400 3         6 my $end_id = $self->_get_node_id($end_node);
401 3         1186 $ret = $self->{_bgi}->allPairsShortestPathsJohnson($start_id,$end_id);
402              
403 3         8 return $ret;
404             }
405             #______________________________________________________________________________________________________________
406             # Floyd-Warshall All Pairs Shortest Paths
407             # returns path weight.
408             sub all_pairs_shortest_paths_floyd_warshall {
409 1     1 1 4 my ($self,$start_node,$end_node) = @_;
410 1 50 33     5 return undef unless $start_node && $self->has_node($start_node) && $end_node && $self->has_node($end_node);
      33        
      33        
411            
412 1         2 my $ret;
413 1         2 my $start_id = $self->_get_node_id($start_node);
414 1         3 my $end_id = $self->_get_node_id($end_node);
415 1         462 $ret = $self->{_bgi}->allPairsShortestPathsFloydWarshall($start_id,$end_id);
416              
417 1         4 return $ret;
418             }
419             #______________________________________________________________________________________________________________
420             ### Minimum Spanning Tree Algorithms ###
421             #______________________________________________________________________________________________________________
422             ### Connected Components Algorithms ###
423             #______________________________________________________________________________________________________________
424             # Connected Components
425             sub connected_components {
426 1     1 1 7 my ($self) = @_;
427 1 50       12 die "connected_components(...) only for undirected graphs." if $self->{_directed};
428            
429 1         2 my @clusters; # list of listrefs that represent the connected clusters
430 1         14 my @components = $self->{_bgi}->connectedComponents();
431 1         4 for(my $node_id=0; $node_id<@components; $node_id++) {
432 0         0 my $cluster = $components[$node_id];
433 0         0 my $node_obj = $self->{_nodes_lookup}->{$node_id};
434 0 0       0 if (defined($node_obj)) {
435 0         0 push @{ $clusters[$cluster] }, $node_obj;
  0         0  
436             }
437             }
438 1 50       4 shift @clusters if !defined($clusters[0]); # remove empty 0 node (we use non-zero indexing for node ids)
439 1         5 return \@clusters;
440             }
441             #______________________________________________________________________________________________________________
442              
443              
444             #
445             #
446              
447             1;
448             __END__