1 package PathsTraversal; 2 # 3 # $RCSfile: PathsTraversal.pm,v $ 4 # $Date: 2008/04/19 16:11:49 $ 5 # $Revision: 1.12 $ 6 # 7 # Author: Manish Sud <msud@san.rr.com> 8 # 9 # Copyright (C) 2004-2008 Manish Sud. All rights reserved. 10 # 11 # This file is part of MayaChemTools. 12 # 13 # MayaChemTools is free software; you can redistribute it and/or modify it under 14 # the terms of the GNU Lesser General Public License as published by the Free 15 # Software Foundation; either version 3 of the License, or (at your option) any 16 # later version. 17 # 18 # MayaChemTools is distributed in the hope that it will be useful, but without 19 # any warranty; without even the implied warranty of merchantability of fitness 20 # for a particular purpose. See the GNU Lesser General Public License for more 21 # details. 22 # 23 # You should have received a copy of the GNU Lesser General Public License 24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 26 # Boston, MA, 02111-1307, USA. 27 # 28 use 5.006; 29 use strict; 30 use Carp; 31 use Exporter; 32 use Graph; 33 use Graph::Path; 34 35 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 36 37 $VERSION = '1.00'; 38 @ISA = qw(Exporter); 39 @EXPORT = qw(); 40 @EXPORT_OK = qw(); 41 42 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 43 44 # Setup class variables... 45 my($ClassName); 46 _InitializeClass(); 47 48 # Overload Perl functions... 49 use overload '""' => 'StringifyPathsTraversal'; 50 51 # Class constructor... 52 sub new { 53 my($Class, $Graph) = @_; 54 55 # Initialize object... 56 my $This = {}; 57 bless $This, ref($Class) || $Class; 58 $This->_InitializePathsTraversal($Graph); 59 60 return $This; 61 } 62 63 # Initialize object data... 64 sub _InitializePathsTraversal { 65 my($This, $Graph) = @_; 66 67 # Graph object... 68 $This->{Graph} = $Graph; 69 70 # Traversal mode: Vertex or Path 71 $This->{TraversalMode} = ''; 72 73 # Traversal type: DFS, DFSWithLimit, BFS, BFSWithLimit... 74 $This->{TraversalType} = ''; 75 76 # For finding root vertex and controlling search... 77 my(@VertexIDs); 78 @VertexIDs = $This->{Graph}->GetVertices(); 79 %{$This->{VerticesToVisit}} = (); 80 @{$This->{VerticesToVisit}}{ @VertexIDs } = @VertexIDs; 81 82 # Root vertex of all visited vertices... 83 %{$This->{VerticesRoots}} = (); 84 85 # Visited vertices... 86 %{$This->{VisitedVertices}} = (); 87 88 # Finished vertices... 89 %{$This->{FinishedVertices}} = (); 90 91 # List of active vertices during DFS/BFS search... 92 @{$This->{ActiveVertices}} = (); 93 94 # List of ordered vertices traversed during DFS/BFS search... 95 @{$This->{Vertices}} = (); 96 97 # Vertex neighbors during traversal... 98 %{$This->{VerticesNeighbors}} = (); 99 100 # Vertices depth from root... 101 %{$This->{VerticesDepth}} = (); 102 103 # Predecessor of each vertex during vertex traversal. For root vertex, it's root itself... 104 %{$This->{VerticesPredecessors}} = (); 105 106 # Successors of each vertex during vertex traversal... 107 %{$This->{VerticesSuccessors}} = (); 108 109 # Vertices at different neighborhood levels during vertex traversal... 110 @{$This->{VerticesNeighborhoods}} = (); 111 112 # Visited edges during Path TraversalMode... 113 %{$This->{VisitedEdges}} = (); 114 %{$This->{VisitedEdges}->{From}} = (); 115 %{$This->{VisitedEdges}->{To}} = (); 116 117 # Vertex path during Path TraversalMode... 118 %{$This->{VerticesPaths}} = (); 119 120 # Allow cycles in paths during Path TraversalMode... 121 $This->{AllowPathCycles} = 1; 122 123 # Cycle closure vertices during Path TraversalMode... 124 %{$This->{CycleClosureVertices}} = (); 125 126 # Paths traversed during search... 127 @{$This->{Paths}} = (); 128 129 return $This; 130 } 131 132 # Initialize class ... 133 sub _InitializeClass { 134 #Class name... 135 $ClassName = __PACKAGE__; 136 } 137 138 # Perform a depth first search (DFS)... 139 # 140 sub PerformDepthFirstSearch { 141 my($This, $RootVertexID) = @_; 142 143 if (defined $RootVertexID) { 144 if (!$This->{Graph}->HasVertex($RootVertexID)) { 145 carp "Warning: ${ClassName}->PerformDepthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; 146 return undef; 147 } 148 } 149 return $This->_PerformVertexSearch("DFS", $RootVertexID); 150 } 151 152 # Perform a depth first search (DFS) with limit on depth... 153 # 154 sub PerformDepthFirstSearchWithLimit { 155 my($This, $DepthLimit, $RootVertexID) = @_; 156 157 if (!defined $DepthLimit) { 158 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; 159 return undef; 160 } 161 if ($DepthLimit < 0) { 162 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; 163 return undef; 164 } 165 if (defined $RootVertexID) { 166 if (!$This->{Graph}->HasVertex($RootVertexID)) { 167 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; 168 return undef; 169 } 170 } 171 return $This->_PerformVertexSearch("DFSWithLimit", $RootVertexID, $DepthLimit); 172 } 173 174 # Perform a breadth first search (BFS)... 175 # 176 sub PerformBreadthFirstSearch { 177 my($This, $RootVertexID) = @_; 178 179 if (defined $RootVertexID) { 180 if (!$This->{Graph}->HasVertex($RootVertexID)) { 181 carp "Warning: ${ClassName}->PerformBreadthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; 182 return undef; 183 } 184 } 185 return $This->_PerformVertexSearch("BFS", $RootVertexID); 186 } 187 188 # Perform a breadth first search (BFS) with limit... 189 # 190 sub PerformBreadthFirstSearchWithLimit { 191 my($This, $DepthLimit, $RootVertexID) = @_; 192 193 if (!defined $DepthLimit) { 194 carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; 195 return undef; 196 } 197 if ($DepthLimit < 0) { 198 carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; 199 return undef; 200 } 201 if (defined $RootVertexID) { 202 if (!$This->{Graph}->HasVertex($RootVertexID)) { 203 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; 204 return undef; 205 } 206 } 207 return $This->_PerformVertexSearch("BFSWithLimit", $RootVertexID, $DepthLimit); 208 } 209 210 # Perform appropriate vertex search... 211 # 212 sub _PerformVertexSearch { 213 my($This, $SearchType, $RootVertexID, $DepthLimit, $TargetVertexID) = @_; 214 215 # Setup search... 216 $This->{TraversalMode} = 'Vertex'; 217 $This->{TraversalType} = $SearchType; 218 219 if (defined $RootVertexID) { 220 $This->{RootVertex} = $RootVertexID; 221 } 222 if (defined $DepthLimit) { 223 $This->{DepthLimit} = $DepthLimit; 224 } 225 if (defined $TargetVertexID) { 226 $This->{TargetVertex} = $TargetVertexID; 227 } 228 229 # Perform search... 230 return $This->_TraverseGraph(); 231 } 232 233 # Perform DFS or BFS traversal with or without any limits... 234 # 235 sub _TraverseGraph { 236 my($This) = @_; 237 my($ProcessingVertices, $CurrentVertexID, $NeighborVertexID, $VertexID); 238 239 if ($This->{TraversalMode} !~ /^(Vertex|Path)$/i) { 240 return $This; 241 } 242 243 $ProcessingVertices = 1; 244 245 VERTICES: while ($ProcessingVertices) { 246 # Set root vertex... 247 if (!@{$This->{ActiveVertices}}) { 248 my($RootVertexID); 249 250 $RootVertexID = $This->_GetRootVertex(); 251 if (!defined $RootVertexID) { 252 $ProcessingVertices = 0; next VERTICES; 253 } 254 $This->_ProcessVisitedVertex($RootVertexID, $RootVertexID); 255 } 256 257 # Get current active vertex... 258 $CurrentVertexID = $This->_GetActiveVertex(); 259 if (!defined $CurrentVertexID) { 260 $ProcessingVertices = 0; next VERTICES; 261 } 262 263 # Get next available neighbor of current vertex... 264 # 265 $NeighborVertexID = $This->_GetNeighborVertex($CurrentVertexID); 266 267 # Process neighbor or current vertex... 268 if (defined $NeighborVertexID) { 269 $This->_ProcessVisitedVertex($NeighborVertexID, $CurrentVertexID); 270 } 271 else { 272 # Finished with all neighbors for current vertex... 273 $This->_ProcessFinishedVertex($CurrentVertexID); 274 } 275 } 276 return $This; 277 } 278 279 # Get root vertex to start the search... 280 # 281 # Notes: 282 # . User specification of root vertex forces traversal in a specific connected component 283 # of graph; To traverse find all connected components, perform traversal without specification of 284 # a root vertex. 285 # 286 sub _GetRootVertex { 287 my($This) = @_; 288 my($RootVertexID); 289 290 # Check for specified root vertex and constrain traversal to specific connected 291 # component by setting root limit... 292 if (exists $This->{RootVertex}) { 293 $RootVertexID = $This->{RootVertex}; 294 delete $This->{RootVertex}; 295 $This->{RootVertexSpecified} = 1; 296 297 return $RootVertexID; 298 } 299 # Traversal limited to connected component containing specified root vertex... 300 if (exists $This->{RootVertexSpecified}) { 301 return undef; 302 } 303 304 # Use first vertex in sorted available vertices list to get root vertex. Vertex 305 # with largest degree could also be used as root vertex. However, for all 306 # practical purposes, any arbitrary vertex can be used as root vertex to 307 # start search for another disconnected component of the graph. 308 # 309 my(@VerticesToVisit); 310 311 $RootVertexID = undef; @VerticesToVisit = (); 312 @VerticesToVisit = sort { $a <=> $b } keys %{$This->{VerticesToVisit}}; 313 if (@VerticesToVisit) { 314 $RootVertexID = $VerticesToVisit[0]; 315 } 316 return $RootVertexID; 317 } 318 319 # Get current or new active vertex for DFS/BFS traversals... 320 # 321 sub _GetActiveVertex { 322 my($This) = @_; 323 my($ActiveVertexID); 324 325 $ActiveVertexID = undef; 326 if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { 327 # For DFS, it's last vertex in LIFO queue... 328 $ActiveVertexID = $This->{ActiveVertices}[-1]; 329 } 330 elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { 331 # For BFS, it's first vertex in FIFO queue... 332 $ActiveVertexID = $This->{ActiveVertices}[0]; 333 } 334 return $ActiveVertexID; 335 } 336 337 # Get available neigbor of specified vertex... 338 # 339 sub _GetNeighborVertex { 340 my($This, $VertexID) = @_; 341 342 # Retrieve neighbors for vertex... 343 if (!exists $This->{VerticesNeighbors}{$VertexID}) { 344 @{$This->{VerticesNeighbors}{$VertexID}} = (); 345 346 if (exists $This->{DepthLimit}) { 347 # Only collect neighbors to visit below specified depth limit... 348 if ($This->{VerticesDepth}{$VertexID} < $This->{DepthLimit}) { 349 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 350 } 351 else { 352 if (!exists $This->{RootVertexSpecified}) { 353 # Mark all other downstream neighbor vertices to be ignored from any further 354 # processing and avoid selection of a new root... 355 $This->_IgnoreDownstreamNeighbors($VertexID); 356 } 357 } 358 } 359 elsif (exists $This->{TargetVertex}) { 360 if ($VertexID != $This->{TargetVertex}) { 361 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 362 } 363 } 364 else { 365 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 366 } 367 } 368 369 if ($This->{TraversalMode} =~ /^Path$/i) { 370 # Get available neighbor for path search... 371 return $This->_GetNeighborVertexDuringPathTraversal($VertexID); 372 } 373 elsif ($This->{TraversalMode} =~ /^Vertex$/i) { 374 # Get unvisited neighbor for vertex search... 375 return $This->_GetNeighborVertexDuringVertexTraversal($VertexID); 376 } 377 return undef; 378 } 379 380 # Get unvisited neigbor of specified vertex during vertex traversal... 381 # 382 sub _GetNeighborVertexDuringVertexTraversal { 383 my($This, $VertexID) = @_; 384 my($NeighborVertexID, $UnvisitedNeighborVertexID); 385 386 # Get unvisited neighbor... 387 $UnvisitedNeighborVertexID = undef; 388 NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { 389 if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { 390 $UnvisitedNeighborVertexID = $NeighborVertexID; 391 last NEIGHBOR; 392 } 393 } 394 return $UnvisitedNeighborVertexID; 395 } 396 397 # Get available neigbor of specified vertex during path traversal... 398 # 399 sub _GetNeighborVertexDuringPathTraversal { 400 my($This, $VertexID) = @_; 401 my($NeighborVertexID, $UnvisitedNeighborVertexID); 402 403 # Get unvisited neighbor... 404 $UnvisitedNeighborVertexID = undef; 405 NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { 406 if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { 407 # An unvisited vertex... 408 $UnvisitedNeighborVertexID = $NeighborVertexID; 409 last NEIGHBOR; 410 } 411 # Look for any unvisited edge back to visited vertex... 412 if ($This->_IsVisitedEdge($VertexID, $NeighborVertexID) || $This->_IsVisitedEdge($NeighborVertexID, $VertexID)) { 413 next NEIGHBOR; 414 } 415 # Check its depth... 416 if (exists $This->{DepthLimit}) { 417 if (($This->{VerticesDepth}{$VertexID} + 1) >= $This->{DepthLimit}) { 418 next NEIGHBOR; 419 } 420 } 421 422 # It's the edge final edge of a cycle in case $NeighborVertexID is already in the path; otherwise, it's 423 # part of the path from a different direction in a cycle or a left over vertex during Limit search. 424 # 425 # Allow cycle or edge selection based on cycles and shared edges parameters... 426 # 427 if ($This->_IsCycleClosureEdge($VertexID, $NeighborVertexID)) { 428 if ($This->{AllowPathCycles}) { 429 $This->{CycleClosureVertices}{$NeighborVertexID} = 1; 430 $UnvisitedNeighborVertexID = $NeighborVertexID; 431 last NEIGHBOR; 432 } 433 } 434 else { 435 $UnvisitedNeighborVertexID = $NeighborVertexID; 436 last NEIGHBOR; 437 } 438 } 439 return $UnvisitedNeighborVertexID; 440 } 441 442 # Process visited vertex... 443 # 444 sub _ProcessVisitedVertex { 445 my($This, $VertexID, $PredecessorVertexID) = @_; 446 447 if (!exists $This->{VisitedVertices}{$VertexID}) { 448 # Add it to active vertices list... 449 push @{$This->{ActiveVertices}}, $VertexID; 450 451 # Mark vertex as visited vertex and take it out from the list of vertices to visit... 452 $This->{VisitedVertices}{$VertexID} = 1; 453 delete $This->{VerticesToVisit}{$VertexID}; 454 } 455 456 # Set up root vertex, predecessor vertex and distance from root... 457 if ($VertexID == $PredecessorVertexID) { 458 $This->{VerticesRoots}{$VertexID} = $VertexID; 459 460 $This->{VerticesPredecessors}{$VertexID} = $VertexID; 461 if (!exists $This->{VerticesSuccessors}{$VertexID}) { 462 @{$This->{VerticesSuccessors}{$VertexID}} = (); 463 } 464 465 $This->{VerticesDepth}{$VertexID} = 0; 466 467 if ($This->{TraversalMode} =~ /^Path$/i) { 468 $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); 469 } 470 } 471 else { 472 $This->{VerticesRoots}{$VertexID} = $This->{VerticesRoots}{$PredecessorVertexID}; 473 474 $This->{VerticesPredecessors}{$VertexID} = $PredecessorVertexID; 475 if (!exists $This->{VerticesSuccessors}{$PredecessorVertexID}) { 476 @{$This->{VerticesSuccessors}{$PredecessorVertexID}} = (); 477 } 478 push @{$This->{VerticesSuccessors}{$PredecessorVertexID}}, $VertexID; 479 480 if (!exists $This->{VerticesDepth}{$VertexID}) { 481 $This->{VerticesDepth}{$VertexID} = $This->{VerticesDepth}{$PredecessorVertexID} + 1; 482 } 483 484 if ($This->{TraversalMode} =~ /^Path$/i) { 485 $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); 486 $This->_ProcessVisitedEdge($PredecessorVertexID, $VertexID); 487 } 488 } 489 return $This; 490 } 491 492 # Process visited path... 493 # 494 sub _ProcessVisitedPath { 495 my($This, $VertexID, $PredecessorVertexID) = @_; 496 497 # Initialize VerticesPath... 498 if (!exists $This->{VerticesPaths}{$VertexID}) { 499 @{$This->{VerticesPaths}{$VertexID}} = (); 500 } 501 502 if ($VertexID == $PredecessorVertexID) { 503 # Starting of a path from root... 504 push @{$This->{VerticesPaths}{$VertexID}}, $VertexID; 505 } 506 else { 507 # Setup path for a vertex using path information from predecessor vertex... 508 if (exists $This->{CycleClosureVertices}{$PredecessorVertexID}) { 509 # Start of a new path from predecessor vertex... 510 push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexID}-${VertexID}"; 511 } 512 else { 513 my($PredecessorVertexPath); 514 for $PredecessorVertexPath (@{$This->{VerticesPaths}{$PredecessorVertexID}}) { 515 push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexPath}-${VertexID}"; 516 } 517 } 518 } 519 return $This; 520 } 521 522 # Process visited edge... 523 # 524 sub _ProcessVisitedEdge { 525 my($This, $VertexID1, $VertexID2) = @_; 526 527 if (!exists $This->{VisitedEdges}->{From}->{$VertexID1}) { 528 %{$This->{VisitedEdges}->{From}->{$VertexID1}} = (); 529 } 530 $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2} = $VertexID2; 531 532 if (!exists $This->{VisitedEdges}->{To}->{$VertexID2}) { 533 %{$This->{VisitedEdges}->{To}->{$VertexID2}} = (); 534 } 535 $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1} = $VertexID1; 536 537 return $This; 538 } 539 540 # Finished processing active vertex... 541 # 542 sub _ProcessFinishedVertex { 543 my($This, $VertexID) = @_; 544 545 if (!exists $This->{FinishedVertices}{$VertexID}) { 546 $This->{FinishedVertices}{$VertexID} = $VertexID; 547 # Add vertex to list of vertices found by traversal... 548 push @{$This->{Vertices}}, $VertexID; 549 } 550 551 # Any active vertices left... 552 if (!@{$This->{ActiveVertices}}) { 553 return $This; 554 } 555 556 # Take it off active vertices list... 557 if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { 558 # For DFS, it's last vertex in LIFO queue... 559 pop @{$This->{ActiveVertices}}; 560 } 561 elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { 562 # For BFS, it's first vertex in FIFO queue... 563 shift @{$This->{ActiveVertices}}; 564 } 565 return $This; 566 } 567 568 # Mark all other downstream neighbor vertices to be ignored from any further 569 # processing... 570 # 571 sub _IgnoreDownstreamNeighbors { 572 my($This, $VertexID, $PredecessorVertexID) = @_; 573 574 if (exists $This->{VerticesToVisit}{$VertexID}) { 575 # Mark vertex as visited vertex and take it out from the list of vertices to visit... 576 $This->{VisitedVertices}{$VertexID} = 1; 577 delete $This->{VerticesToVisit}{$VertexID}; 578 579 if (defined($PredecessorVertexID) && $This->{TraversalMode} =~ /^Path$/i) { 580 $This->_ProcessVisitedEdge($VertexID, $PredecessorVertexID); 581 } 582 } 583 my($NeighborVertexID, @NeighborsVertexIDs); 584 585 @NeighborsVertexIDs = (); 586 @NeighborsVertexIDs = $This->{Graph}->GetNeighbors($VertexID); 587 NEIGHBOR: for $NeighborVertexID (@NeighborsVertexIDs) { 588 if (!exists $This->{VerticesToVisit}{$NeighborVertexID}) { 589 # Avoid going back to predecessor vertex which has already been ignored... 590 next NEIGHBOR; 591 } 592 $This->_IgnoreDownstreamNeighbors($NeighborVertexID, $VertexID); 593 } 594 return $This; 595 } 596 597 # Is it a visited edge? 598 # 599 sub _IsVisitedEdge { 600 my($This, $VertexID1, $VertexID2) = @_; 601 602 if (exists $This->{VisitedEdges}->{From}->{$VertexID1}) { 603 if (exists $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2}) { 604 return 1; 605 } 606 } 607 elsif (exists $This->{VisitedEdges}->{To}->{$VertexID2}) { 608 if (exists $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1}) { 609 return 1; 610 } 611 } 612 return 0; 613 } 614 615 # Is it a cycle closure edge? 616 # 617 # Notes: 618 # . Presence of VertexID2 in DFS path traversed for VertexID1 make it a cycle 619 # closure edge... 620 # 621 sub _IsCycleClosureEdge { 622 my($This, $VertexID1, $VertexID2) = @_; 623 624 if (!exists $This->{VerticesPaths}{$VertexID1}) { 625 return 0; 626 } 627 my($Path); 628 for $Path (@{$This->{VerticesPaths}{$VertexID1}}) { 629 if (($Path =~ /-$VertexID2-/ || $Path =~ /^$VertexID2-/ || $Path =~ /-$VertexID2$/)) { 630 return 1; 631 } 632 } 633 return 0; 634 } 635 636 # Search paths starting from a specified vertex with no sharing of edges in paths traversed. 637 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 638 # completing the cycle. 639 # 640 sub PerformPathsSearch { 641 my($This, $StartVertexID, $AllowCycles) = @_; 642 643 # Make sure start vertex is defined... 644 if (!defined $StartVertexID) { 645 carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Start vertex must be specified..."; 646 return undef; 647 } 648 649 # Make sure start vertex is valid... 650 if (!$This->{Graph}->HasVertex($StartVertexID)) { 651 carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; 652 return undef; 653 } 654 655 if (!defined $AllowCycles) { 656 $AllowCycles = 1; 657 } 658 659 # Perform paths search... 660 return $This->_PerformPathsSearch("AllLengths", $StartVertexID, $AllowCycles); 661 } 662 663 # Search paths starting from a specified vertex with length upto a specified length 664 # with no sharing of edges in paths traversed... 665 # 666 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 667 # completing the cycle. 668 # 669 sub PerformPathsSearchWithLengthUpto { 670 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 671 672 # Make sure both start vertex and length are defined... 673 if (!defined $StartVertexID) { 674 carp "Warning: ${ClassName}->PerformPathsSearchWithLengthUpto: No paths search performed: Start vertex must be specified..."; 675 return undef; 676 } 677 if (!defined $Length) { 678 carp "Warning: ${ClassName}->PerformPathsSearchWithLengthUpto: No paths search performed: Length must be specified..."; 679 return undef; 680 } 681 682 if (!defined $AllowCycles) { 683 $AllowCycles = 1; 684 } 685 686 # Make sure both start vertex and length are valid... 687 if (!$This->{Graph}->HasVertex($StartVertexID)) { 688 carp "Warning: ${ClassName}->PerformPathsSearchWithLengthUpto: No paths search performed: Vertex $StartVertexID doesn't exist..."; 689 return undef; 690 } 691 692 if ($Length < 1) { 693 carp "Warning: ${ClassName}->PerformPathsSearchWithLengthUpto: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; 694 return undef; 695 } 696 697 # Perform paths search... 698 return $This->_PerformPathsSearch("LengthUpto", $StartVertexID, $AllowCycles, $Length); 699 } 700 701 # Search all paths starting from a specified vertex with sharing of edges in paths traversed... 702 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 703 # completing the cycle. 704 # 705 sub PerformAllPathsSearch { 706 my($This, $StartVertexID, $AllowCycles) = @_; 707 708 # Make sure start vertex is defined... 709 if (!defined $StartVertexID) { 710 carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Start vertex must be specified..."; 711 return undef; 712 } 713 714 # Make sure start vertex is valid... 715 if (!$This->{Graph}->HasVertex($StartVertexID)) { 716 carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; 717 return undef; 718 } 719 720 if (!defined $AllowCycles) { 721 $AllowCycles = 1; 722 } 723 724 # Perform paths search... 725 return $This->_PerformAllPathsSearch("AllLengths", $StartVertexID, $AllowCycles); 726 } 727 728 # Search all paths starting from a specified vertex with length upto a specified length with sharing of 729 # edges in paths traversed. 730 # 731 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 732 # completing the cycle. 733 # 734 sub PerformAllPathsSearchWithLengthUpto { 735 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 736 737 # Make sure both start vertex and length are defined... 738 if (!defined $StartVertexID) { 739 carp "Warning: ${ClassName}->PerformAllPathsSearchWithLengthUpto: No paths search performed: Start vertex must be specified..."; 740 return undef; 741 } 742 if (!defined $Length) { 743 carp "Warning: ${ClassName}->PerformAllPathsSearchWithLengthUpto: No paths search performed: Length must be specified..."; 744 return undef; 745 } 746 747 if (!defined $AllowCycles) { 748 $AllowCycles = 1; 749 } 750 751 # Make sure both start vertex and length are valid... 752 if (!$This->{Graph}->HasVertex($StartVertexID)) { 753 carp "Warning: ${ClassName}->PerformAllPathsSearchWithLengthUpto: No paths search performed: Vertex $StartVertexID doesn't exist..."; 754 return undef; 755 } 756 757 if ($Length < 1) { 758 carp "Warning: ${ClassName}->PerformAllPathsSearchWithLengthUpto: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; 759 return undef; 760 } 761 762 # Perform paths search... 763 return $This->_PerformAllPathsSearch("LengthUpto", $StartVertexID, $AllowCycles, $Length); 764 } 765 766 # Search paths between two vertices... 767 # 768 sub PerformPathsSearchBetween { 769 my($This, $StartVertexID, $EndVertexID) = @_; 770 771 # Make sure start and end vertices are defined... 772 if (!defined $StartVertexID) { 773 carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: Start vertex must be specified..."; 774 return undef; 775 } 776 if (!defined $EndVertexID) { 777 carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: EndVertex vertex must be specified..."; 778 return undef; 779 } 780 # Make sure start and end vertices are valid... 781 if (!$This->{Graph}->HasVertex($StartVertexID)) { 782 carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $StartVertexID doesn't exist..."; 783 return undef; 784 } 785 if (!$This->{Graph}->HasVertex($EndVertexID)) { 786 carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $EndVertexID doesn't exist..."; 787 return undef; 788 } 789 790 # Perform paths search... 791 return $This->_PerformPathsSearchBetween($StartVertexID, $EndVertexID); 792 } 793 794 # Search paths starting from root vertex with no sharing of edges... 795 # 796 # Notes: 797 # . Possible paths searche modes are: DFSPathsWithLimit, DFSPaths. And each 798 # of these modes supports any combination of two options: CommonEdges, Cycles. 799 # Default for CommonEdges - No; Cycles - No. 800 # 801 sub _PerformPathsSearch { 802 my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; 803 804 # Perform DFS path search... 805 806 $This->{TraversalMode} = 'Path'; 807 808 if ($Mode =~ /^LengthUpto$/i) { 809 my($DepthLimit); 810 811 $DepthLimit = $Length - 1; 812 $This->{TraversalType} = 'DFSWithLimit'; 813 $This->{DepthLimit} = $DepthLimit; 814 } 815 else { 816 $This->{TraversalType} = 'DFS'; 817 } 818 if (defined $RootVertexID) { 819 $This->{RootVertex} = $RootVertexID; 820 } 821 822 $This->{AllowPathCycles} = $AllowCycles; 823 824 # Perform search... 825 $This->_TraverseGraph(); 826 827 # Make sure traversal did get the root vertex... 828 if (!exists $This->{VerticesDepth}{$RootVertexID}) { 829 return $This; 830 } 831 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); 832 833 return $This; 834 } 835 836 # Search all paths starting from root vertex with sharing of edges... 837 # 838 sub _PerformAllPathsSearch { 839 my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; 840 841 # Perform DFS path search... 842 843 $This->{TraversalMode} = 'AllPaths'; 844 845 if ($Mode =~ /^LengthUpto$/i) { 846 my($DepthLimit); 847 848 $DepthLimit = $Length - 1; 849 $This->{TraversalType} = 'DFSWithLimit'; 850 $This->{DepthLimit} = $DepthLimit; 851 } 852 else { 853 $This->{TraversalType} = 'DFS'; 854 } 855 $This->{RootVertex} = $RootVertexID; 856 $This->{AllowPathCycles} = $AllowCycles; 857 858 # Traverse all paths search using DFS search... 859 $This->_TraverseAllPathsInGraph(); 860 861 return $This; 862 } 863 864 # Travese all paths in graph starting from a specified root vertex... 865 # 866 sub _TraverseAllPathsInGraph { 867 my($This) = @_; 868 869 if ($This->{TraversalMode} !~ /^AllPaths$/i) { 870 return $This; 871 } 872 my($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); 873 874 $CurrentVertexID = $This->{RootVertex}; 875 $PredecessorVertexID = $CurrentVertexID; 876 $CurrentDepth = 0; 877 $CurrentPath = "$CurrentVertexID"; 878 879 $This->_TraverseAllPaths($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); 880 881 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); 882 883 return $This; 884 } 885 886 # Traverse and collect all paths recuresively.. 887 # 888 sub _TraverseAllPaths { 889 my($This, $CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath) = @_; 890 891 # Save path traversed for current vertex.. 892 if (!exists $This->{VerticesPaths}{$CurrentVertexID}) { 893 @{$This->{VerticesPaths}{$CurrentVertexID}} = (); 894 } 895 push @{$This->{VerticesPaths}{$CurrentVertexID}}, $CurrentPath; 896 897 $CurrentDepth++; 898 if (exists $This->{DepthLimit}) { 899 if ($CurrentDepth > $This->{DepthLimit}) { 900 # Nothing more to do... 901 return $This; 902 } 903 } 904 my($NeighborVertexID, $NewPath); 905 906 NEIGHBOR: for $NeighborVertexID ($This->{Graph}->GetNeighbors($CurrentVertexID)) { 907 if ($NeighborVertexID == $PredecessorVertexID) { 908 next NEIGHBOR; 909 } 910 if ($This->_IsVertexInTraversedPath($NeighborVertexID, $CurrentPath)) { 911 # It's a cycle... 912 if ($This->{AllowPathCycles}) { 913 $NewPath = "${CurrentPath}-${NeighborVertexID}"; 914 if (!exists $This->{VerticesPaths}{$NeighborVertexID}) { 915 @{$This->{VerticesPaths}{$NeighborVertexID}} = (); 916 } 917 push @{$This->{VerticesPaths}{$NeighborVertexID}}, $NewPath; 918 } 919 next NEIGHBOR; 920 } 921 $NewPath = "${CurrentPath}-${NeighborVertexID}"; 922 $This->_TraverseAllPaths($NeighborVertexID, $CurrentVertexID, $CurrentDepth, $NewPath); 923 } 924 return $This; 925 } 926 927 # Is vertex already in traversed path? 928 # 929 sub _IsVertexInTraversedPath { 930 my($This, $VertexID, $Path) = @_; 931 932 return ($Path =~ /-$VertexID-/ || $Path =~ /^$VertexID-/ || $Path =~ /-$VertexID$/) ? 1 : 0; 933 } 934 935 # Collect paths traversed during Path TraversalMode... 936 # 937 sub _CollectPathsTraversedDuringPathsSearch { 938 my($This) = @_; 939 my($VertexID, @Paths, @SortedPaths); 940 941 @Paths = (); @SortedPaths = (); 942 943 # Create path objects from path vertex strings... 944 for $VertexID (keys %{$This->{VerticesPaths}}) { 945 push @Paths, map { new Path(split /-/, $_) } @{$This->{VerticesPaths}{$VertexID}}; 946 } 947 948 # Sort paths in ascending order of lengths... 949 push @SortedPaths, sort { $a->GetLength() <=> $b->GetLength() } @Paths; 950 951 return @SortedPaths; 952 } 953 954 # Collect paths... 955 # 956 sub _CollectPathsTraversedDuringVertexSearch { 957 my($This, $RootVertexID) = @_; 958 my($Depth, @Paths, @VerticesAtDepth); 959 @Paths = (); 960 961 # Get vertices at specific depths... 962 @VerticesAtDepth = (); 963 @VerticesAtDepth = $This->_CollectVerticesAtSpecificDepths(); 964 if (!@VerticesAtDepth) { 965 return @Paths; 966 } 967 968 # Make sure search found only one root vertex and it corresponds to 969 # what was specified... 970 $Depth = 0; 971 if ((@{$VerticesAtDepth[$Depth]} > 1) || ($VerticesAtDepth[$Depth][0] != $RootVertexID)) { 972 carp "Warning: ${ClassName}->_PerformPathsSearch: No paths found: Root vertex, $VerticesAtDepth[$Depth][0], identified by paths traversal doen't match specified root vertex $RootVertexID..."; 973 return @Paths; 974 } 975 976 # Setup root vertex at depth 0. And set its path... 977 my($Path, $VertexID, $SuccessorVertexID, @VertexIDs, %PathAtVertex); 978 %PathAtVertex = (); 979 $PathAtVertex{$RootVertexID} = new Path($RootVertexID); 980 981 for $Depth (0 .. $#VerticesAtDepth) { 982 # Go over all vertices at current depth... 983 VERTEX: for $VertexID (@{$VerticesAtDepth[$Depth]}) { 984 if (!exists $This->{VerticesSuccessors}{$VertexID}) { 985 next VERTEX; 986 } 987 # Get vertices for current path... 988 @VertexIDs = (); 989 push @VertexIDs, $PathAtVertex{$VertexID}->GetVertices; 990 991 # Expand path to successor vertex found during traversal... 992 for $SuccessorVertexID (@{$This->{VerticesSuccessors}{$VertexID}}) { 993 $Path = new Path(@VertexIDs); 994 $Path->AddVertex($SuccessorVertexID); 995 $PathAtVertex{$SuccessorVertexID} = $Path; 996 } 997 } 998 } 999 # Sort paths in ascending order of lengths... 1000 push @Paths, sort { $a->GetLength() <=> $b->GetLength() } values %PathAtVertex; 1001 1002 return @Paths; 1003 } 1004 1005 # Collect vertices at specific depths. Depth values start from 0... 1006 # 1007 sub _CollectVerticesAtSpecificDepths { 1008 my($This) = @_; 1009 my($VertexID, $Depth, @VerticesAtDepth); 1010 1011 @VerticesAtDepth = (); 1012 while (($VertexID, $Depth) = each %{$This->{VerticesDepth}}) { 1013 push @{$VerticesAtDepth[$Depth]}, $VertexID; 1014 } 1015 return @VerticesAtDepth; 1016 } 1017 1018 # Search paths between two vertices... 1019 # 1020 sub _PerformPathsSearchBetween { 1021 my($This, $RootVertexID, $TargetVertexID) = @_; 1022 my($DepthLimit); 1023 1024 # Perform a targeted DFS search... 1025 $DepthLimit = undef; 1026 $This->_PerformVertexSearch("DFS", $RootVertexID, $DepthLimit, $TargetVertexID); 1027 1028 my($Path); 1029 $Path = $This->_CollectPathBetween($RootVertexID, $TargetVertexID); 1030 1031 if (defined $Path) { 1032 push @{$This->{Paths}}, $Path; 1033 } 1034 return $This; 1035 } 1036 1037 # Collect path between root and target vertex after the search... 1038 # 1039 sub _CollectPathBetween { 1040 my($This, $RootVertexID, $TargetVertexID) = @_; 1041 1042 # Does a path from root to target vertex exist? 1043 if (!(exists($This->{VerticesRoots}{$TargetVertexID}) && ($This->{VerticesRoots}{$TargetVertexID} == $RootVertexID))) { 1044 return undef; 1045 } 1046 1047 # Add target vertex ID path vertices... 1048 my($VertexID, $Path, @VertexIDs); 1049 @VertexIDs = (); 1050 $VertexID = $TargetVertexID; 1051 push @VertexIDs, $VertexID; 1052 1053 # Backtrack to root vertex ID... 1054 while ($This->{VerticesPredecessors}{$VertexID} != $VertexID) { 1055 $VertexID = $This->{VerticesPredecessors}{$VertexID}; 1056 push @VertexIDs, $VertexID; 1057 } 1058 1059 # Create path from target to root and reverse it... 1060 $Path = new Path(@VertexIDs); 1061 $Path->Reverse(); 1062 1063 return $Path; 1064 } 1065 1066 # Search vertices around specified root vertex with in specific neighborhood radius... 1067 # 1068 sub PerformNeighborhoodVerticesSearchWithRadiusUpto { 1069 my($This, $StartVertexID, $Radius) = @_; 1070 1071 # Make sure both start vertex and radius are defined... 1072 if (!defined $StartVertexID) { 1073 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Start vertex must be specified..."; 1074 return undef; 1075 } 1076 if (!defined $Radius) { 1077 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Radius must be specified..."; 1078 return undef; 1079 } 1080 1081 # Make sure both start vertex and length are valid... 1082 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1083 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1084 return undef; 1085 } 1086 if ($Radius < 0) { 1087 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Specified radius, $Radius, must be a positive integer..."; 1088 return undef; 1089 } 1090 1091 # Perform vertices search... 1092 return $This->_PerformNeighborhoodVerticesSearch("RadiusUpto", $StartVertexID, $Radius); 1093 } 1094 1095 # Search vertices around specified root vertex... 1096 # 1097 sub PerformNeighborhoodVerticesSearch { 1098 my($This, $StartVertexID) = @_; 1099 1100 # Make sure start vertex is defined... 1101 if (!defined $StartVertexID) { 1102 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Start vertex must be specified..."; 1103 return undef; 1104 } 1105 1106 # Make sure start vertex is valid... 1107 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1108 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1109 return undef; 1110 } 1111 # Perform vertices search... 1112 return $This->_PerformNeighborhoodVerticesSearch("AllRadii", $StartVertexID); 1113 } 1114 1115 # Search vertices at successive neighborhood radii levels... 1116 # 1117 sub _PerformNeighborhoodVerticesSearch { 1118 my($This, $Mode, $RootVertexID, $Radius) = @_; 1119 1120 # Perform BFS search... 1121 if ($Mode =~ /^RadiusUpto$/i) { 1122 my($DepthLimit); 1123 $DepthLimit = $Radius; 1124 $This->_PerformVertexSearch("BFSWithLimit", $RootVertexID, $DepthLimit) 1125 } 1126 elsif ($Mode =~ /^(AllRadii)$/i) { 1127 $This->_PerformVertexSearch("BFS", $RootVertexID) 1128 } 1129 # Make sure traversal did get the root vertex... 1130 if (!exists $This->{VerticesDepth}{$RootVertexID}) { 1131 return $This; 1132 } 1133 push @{$This->{VerticesNeighborhoods}}, $This->_CollectVerticesAtSpecificDepths(); 1134 1135 return $This; 1136 } 1137 1138 # Get orderded list of vertices after DFS/BFS search... 1139 # 1140 sub GetVertices { 1141 my($This) = @_; 1142 1143 return wantarray ? @{$This->{Vertices}} : scalar @{$This->{Vertices}}; 1144 } 1145 1146 # Get a hash list containing vertex and root vertex as key/value pair for all vertices 1147 # ordered using DFS/BFS search available via GetVertices method... 1148 # 1149 sub GetVerticesRoots { 1150 my($This) = @_; 1151 1152 return %{$This->{VerticesRoots}}; 1153 } 1154 1155 # Get a list containing lists of vertices in connected components of graph after DFS/BFS 1156 # search... 1157 # 1158 # Note: 1159 # . List is sorted in descending order of number of vertices in each connected component. 1160 # 1161 sub GetConnectedComponentsVertices { 1162 my($This) = @_; 1163 my($VertexID, $VertexRoot, @ConnectedVertices, %VerticesAtRoot); 1164 1165 @ConnectedVertices = (); 1166 %VerticesAtRoot = (); 1167 for $VertexID (@{$This->{Vertices}}) { 1168 $VertexRoot = $This->{VerticesRoots}{$VertexID}; 1169 if (!exists $VerticesAtRoot{$VertexRoot}) { 1170 @{$VerticesAtRoot{$VertexRoot}} = (); 1171 } 1172 push @{$VerticesAtRoot{$VertexRoot}}, $VertexID