1 package Graph::PathsTraversal; 2 # 3 # File: PathsTraversal.pm 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2024 Manish Sud. All rights reserved. 7 # 8 # This file is part of MayaChemTools. 9 # 10 # MayaChemTools is free software; you can redistribute it and/or modify it under 11 # the terms of the GNU Lesser General Public License as published by the Free 12 # Software Foundation; either version 3 of the License, or (at your option) any 13 # later version. 14 # 15 # MayaChemTools is distributed in the hope that it will be useful, but without 16 # any warranty; without even the implied warranty of merchantability of fitness 17 # for a particular purpose. See the GNU Lesser General Public License for more 18 # details. 19 # 20 # You should have received a copy of the GNU Lesser General Public License 21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 23 # Boston, MA, 02111-1307, USA. 24 # 25 26 use strict; 27 use Carp; 28 use Exporter; 29 use Graph; 30 use Graph::Path; 31 32 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 33 34 @ISA = qw(Exporter); 35 @EXPORT = qw(); 36 @EXPORT_OK = qw(); 37 38 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 39 40 # Setup class variables... 41 my($ClassName); 42 _InitializeClass(); 43 44 # Overload Perl functions... 45 use overload '""' => 'StringifyPathsTraversal'; 46 47 # Class constructor... 48 sub new { 49 my($Class, $Graph) = @_; 50 51 # Initialize object... 52 my $This = {}; 53 bless $This, ref($Class) || $Class; 54 $This->_InitializePathsTraversal($Graph); 55 56 return $This; 57 } 58 59 # Initialize object data... 60 sub _InitializePathsTraversal { 61 my($This, $Graph) = @_; 62 63 # Graph object... 64 $This->{Graph} = $Graph; 65 66 # Traversal mode: Vertex or Path 67 $This->{TraversalMode} = ''; 68 69 # Traversal type: DFS, DFSWithLimit, BFS, BFSWithLimit... 70 $This->{TraversalType} = ''; 71 72 # For finding root vertex and controlling search... 73 my(@VertexIDs); 74 @VertexIDs = $This->{Graph}->GetVertices(); 75 %{$This->{VerticesToVisit}} = (); 76 @{$This->{VerticesToVisit}}{ @VertexIDs } = @VertexIDs; 77 78 # Root vertex of all visited vertices... 79 %{$This->{VerticesRoots}} = (); 80 81 # Visited vertices... 82 %{$This->{VisitedVertices}} = (); 83 84 # Finished vertices... 85 %{$This->{FinishedVertices}} = (); 86 87 # List of active vertices during DFS/BFS search... 88 @{$This->{ActiveVertices}} = (); 89 90 # List of ordered vertices traversed during DFS/BFS search... 91 @{$This->{Vertices}} = (); 92 93 # Vertex neighbors during traversal... 94 %{$This->{VerticesNeighbors}} = (); 95 96 # Vertices depth from root... 97 %{$This->{VerticesDepth}} = (); 98 99 # Predecessor of each vertex during vertex traversal. For root vertex, it's root itself... 100 %{$This->{VerticesPredecessors}} = (); 101 102 # Successors of each vertex during vertex traversal... 103 %{$This->{VerticesSuccessors}} = (); 104 105 # Vertices at different neighborhood levels during vertex traversal... 106 @{$This->{VerticesNeighborhoods}} = (); 107 108 # Vertices, along with their successors, at different neighborhood levels during vertex traversal... 109 @{$This->{VerticesNeighborhoodsWithSuccessors}} = (); 110 111 # Visited edges during Path TraversalMode... 112 %{$This->{VisitedEdges}} = (); 113 %{$This->{VisitedEdges}->{From}} = (); 114 %{$This->{VisitedEdges}->{To}} = (); 115 116 # Vertex path during Path TraversalMode... 117 %{$This->{VerticesPaths}} = (); 118 119 # Allow cycles in paths during VertexNeighborhood TraversalMode. By default, cycles are not allowed 120 # during vertex traversal: a vertex is only visited once during BFS search for neighborhoods. For 121 # neighborhood vertices search during successors identification, vertex cycles are explicity allowed 122 # to indentify all successors. 123 $This->{AllowVertexCycles} = 0; 124 125 # Allow cycles in paths during Path TraversalMode... 126 $This->{AllowPathCycles} = 1; 127 128 # Cycle closure vertices during Path TraversalMode... 129 %{$This->{CycleClosureVertices}} = (); 130 131 # Paths traversed during search... 132 @{$This->{Paths}} = (); 133 134 return $This; 135 } 136 137 # Initialize class ... 138 sub _InitializeClass { 139 #Class name... 140 $ClassName = __PACKAGE__; 141 } 142 143 # Perform a depth first search (DFS)... 144 # 145 sub PerformDepthFirstSearch { 146 my($This, $RootVertexID) = @_; 147 148 if (defined $RootVertexID) { 149 if (!$This->{Graph}->HasVertex($RootVertexID)) { 150 carp "Warning: ${ClassName}->PerformDepthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; 151 return undef; 152 } 153 } 154 return $This->_PerformVertexSearch("DFS", $RootVertexID); 155 } 156 157 # Perform a depth first search (DFS) with limit on depth... 158 # 159 sub PerformDepthFirstSearchWithLimit { 160 my($This, $DepthLimit, $RootVertexID) = @_; 161 162 if (!defined $DepthLimit) { 163 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; 164 return undef; 165 } 166 if ($DepthLimit < 0) { 167 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; 168 return undef; 169 } 170 if (defined $RootVertexID) { 171 if (!$This->{Graph}->HasVertex($RootVertexID)) { 172 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; 173 return undef; 174 } 175 } 176 return $This->_PerformVertexSearch("DFSWithLimit", $RootVertexID, $DepthLimit); 177 } 178 179 # Perform a breadth first search (BFS)... 180 # 181 sub PerformBreadthFirstSearch { 182 my($This, $RootVertexID) = @_; 183 184 if (defined $RootVertexID) { 185 if (!$This->{Graph}->HasVertex($RootVertexID)) { 186 carp "Warning: ${ClassName}->PerformBreadthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; 187 return undef; 188 } 189 } 190 return $This->_PerformVertexSearch("BFS", $RootVertexID); 191 } 192 193 # Perform a breadth first search (BFS) with limit... 194 # 195 sub PerformBreadthFirstSearchWithLimit { 196 my($This, $DepthLimit, $RootVertexID) = @_; 197 198 if (!defined $DepthLimit) { 199 carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; 200 return undef; 201 } 202 if ($DepthLimit < 0) { 203 carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; 204 return undef; 205 } 206 if (defined $RootVertexID) { 207 if (!$This->{Graph}->HasVertex($RootVertexID)) { 208 carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; 209 return undef; 210 } 211 } 212 return $This->_PerformVertexSearch("BFSWithLimit", $RootVertexID, $DepthLimit); 213 } 214 215 # Perform appropriate vertex search... 216 # 217 sub _PerformVertexSearch { 218 my($This, $SearchType, $RootVertexID, $DepthLimit, $TargetVertexID) = @_; 219 220 # Setup search... 221 $This->{TraversalMode} = 'Vertex'; 222 $This->{TraversalType} = $SearchType; 223 224 if (defined $RootVertexID) { 225 $This->{RootVertex} = $RootVertexID; 226 } 227 if (defined $DepthLimit) { 228 $This->{DepthLimit} = $DepthLimit; 229 } 230 if (defined $TargetVertexID) { 231 $This->{TargetVertex} = $TargetVertexID; 232 } 233 234 # Perform search... 235 return $This->_TraverseGraph(); 236 } 237 238 # Perform DFS or BFS traversal with or without any limits... 239 # 240 sub _TraverseGraph { 241 my($This) = @_; 242 my($ProcessingVertices, $CurrentVertexID, $NeighborVertexID, $VertexID); 243 244 if ($This->{TraversalMode} !~ /^(Vertex|Path|VertexNeighborhood)$/i) { 245 return $This; 246 } 247 248 $ProcessingVertices = 1; 249 250 VERTICES: while ($ProcessingVertices) { 251 # Set root vertex... 252 if (!@{$This->{ActiveVertices}}) { 253 my($RootVertexID); 254 255 $RootVertexID = $This->_GetRootVertex(); 256 if (!defined $RootVertexID) { 257 $ProcessingVertices = 0; next VERTICES; 258 } 259 $This->_ProcessVisitedVertex($RootVertexID, $RootVertexID); 260 } 261 262 # Get current active vertex... 263 $CurrentVertexID = $This->_GetActiveVertex(); 264 if (!defined $CurrentVertexID) { 265 $ProcessingVertices = 0; next VERTICES; 266 } 267 268 # Get next available neighbor of current vertex... 269 # 270 $NeighborVertexID = $This->_GetNeighborVertex($CurrentVertexID); 271 272 # Process neighbor or current vertex... 273 if (defined $NeighborVertexID) { 274 $This->_ProcessVisitedVertex($NeighborVertexID, $CurrentVertexID); 275 } 276 else { 277 # Finished with all neighbors for current vertex... 278 $This->_ProcessFinishedVertex($CurrentVertexID); 279 } 280 } 281 return $This; 282 } 283 284 # Get root vertex to start the search... 285 # 286 # Notes: 287 # . User specification of root vertex forces traversal in a specific connected component 288 # of graph; To traverse find all connected components, perform traversal without specification of 289 # a root vertex. 290 # 291 sub _GetRootVertex { 292 my($This) = @_; 293 my($RootVertexID); 294 295 # Check for specified root vertex and constrain traversal to specific connected 296 # component by setting root limit... 297 if (exists $This->{RootVertex}) { 298 $RootVertexID = $This->{RootVertex}; 299 delete $This->{RootVertex}; 300 $This->{RootVertexSpecified} = 1; 301 302 return $RootVertexID; 303 } 304 # Traversal limited to connected component containing specified root vertex... 305 if (exists $This->{RootVertexSpecified}) { 306 return undef; 307 } 308 309 # Use first vertex in sorted available vertices list to get root vertex. Vertex 310 # with largest degree could also be used as root vertex. However, for all 311 # practical purposes, any arbitrary vertex can be used as root vertex to 312 # start search for another disconnected component of the graph. 313 # 314 my(@VerticesToVisit); 315 316 $RootVertexID = undef; @VerticesToVisit = (); 317 @VerticesToVisit = sort { $a <=> $b } keys %{$This->{VerticesToVisit}}; 318 if (@VerticesToVisit) { 319 $RootVertexID = $VerticesToVisit[0]; 320 } 321 return $RootVertexID; 322 } 323 324 # Get current or new active vertex for DFS/BFS traversals... 325 # 326 sub _GetActiveVertex { 327 my($This) = @_; 328 my($ActiveVertexID); 329 330 $ActiveVertexID = undef; 331 if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { 332 # For DFS, it's last vertex in LIFO queue... 333 $ActiveVertexID = $This->{ActiveVertices}[-1]; 334 } 335 elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { 336 # For BFS, it's first vertex in FIFO queue... 337 $ActiveVertexID = $This->{ActiveVertices}[0]; 338 } 339 return $ActiveVertexID; 340 } 341 342 # Get available neigbor of specified vertex... 343 # 344 sub _GetNeighborVertex { 345 my($This, $VertexID) = @_; 346 347 # Retrieve neighbors for vertex... 348 if (!exists $This->{VerticesNeighbors}{$VertexID}) { 349 @{$This->{VerticesNeighbors}{$VertexID}} = (); 350 351 if (exists $This->{DepthLimit}) { 352 # Only collect neighbors to visit below specified depth limit... 353 if ($This->{VerticesDepth}{$VertexID} < $This->{DepthLimit}) { 354 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 355 } 356 else { 357 if (!exists $This->{RootVertexSpecified}) { 358 # Mark all other downstream neighbor vertices to be ignored from any further 359 # processing and avoid selection of a new root... 360 $This->_IgnoreDownstreamNeighbors($VertexID); 361 } 362 } 363 } 364 elsif (exists $This->{TargetVertex}) { 365 if ($VertexID != $This->{TargetVertex}) { 366 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 367 } 368 } 369 else { 370 push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); 371 } 372 } 373 374 if ($This->{TraversalMode} =~ /^Path$/i) { 375 # Get available neighbor for path search... 376 return $This->_GetNeighborVertexDuringPathTraversal($VertexID); 377 } 378 elsif ($This->{TraversalMode} =~ /^Vertex$/i) { 379 # Get unvisited neighbor for vertex search... 380 return $This->_GetNeighborVertexDuringVertexTraversal($VertexID); 381 } 382 elsif ($This->{TraversalMode} =~ /^VertexNeighborhood$/i) { 383 # Get available neighbor during vertex neighborhood search... 384 return $This->_GetNeighborVertexDuringVertexNeighborhoodTraversal($VertexID); 385 } 386 return undef; 387 } 388 389 # Get unvisited neighbor of specified vertex during vertex traversal... 390 # 391 sub _GetNeighborVertexDuringVertexTraversal { 392 my($This, $VertexID) = @_; 393 my($NeighborVertexID, $UnvisitedNeighborVertexID); 394 395 # Get unvisited neighbor... 396 $UnvisitedNeighborVertexID = undef; 397 NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { 398 if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { 399 $UnvisitedNeighborVertexID = $NeighborVertexID; 400 last NEIGHBOR; 401 } 402 } 403 return $UnvisitedNeighborVertexID; 404 } 405 406 # Get available neighbor of specified vertex during vertex neighborhood traversal... 407 # 408 sub _GetNeighborVertexDuringVertexNeighborhoodTraversal { 409 my($This, $VertexID) = @_; 410 my($NeighborVertexID, $UnvisitedNeighborVertexID); 411 412 # Get available neighbor... 413 $UnvisitedNeighborVertexID = undef; 414 NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { 415 if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { 416 $UnvisitedNeighborVertexID = $NeighborVertexID; 417 last NEIGHBOR; 418 } 419 # Look for any unvisited edge back to visited vertex... 420 if ($This->_IsVisitedEdge($VertexID, $NeighborVertexID) || $This->_IsVisitedEdge($NeighborVertexID, $VertexID)) { 421 next NEIGHBOR; 422 } 423 # Check its depth... 424 if (exists $This->{DepthLimit}) { 425 if (($This->{VerticesDepth}{$VertexID} + 1) > $This->{DepthLimit}) { 426 next NEIGHBOR; 427 } 428 } 429 # Its an edge that makes a cycle during BFS search... 430 if ($This->{AllowVertexCycles}) { 431 $This->{CycleClosureVertices}{$NeighborVertexID} = 1; 432 $UnvisitedNeighborVertexID = $NeighborVertexID; 433 last NEIGHBOR; 434 } 435 } 436 return $UnvisitedNeighborVertexID; 437 } 438 439 # Get available neighbor of specified vertex during path traversal... 440 # 441 sub _GetNeighborVertexDuringPathTraversal { 442 my($This, $VertexID) = @_; 443 my($NeighborVertexID, $UnvisitedNeighborVertexID); 444 445 # Get unvisited neighbor... 446 $UnvisitedNeighborVertexID = undef; 447 NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { 448 if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { 449 # An unvisited vertex... 450 $UnvisitedNeighborVertexID = $NeighborVertexID; 451 last NEIGHBOR; 452 } 453 # Look for any unvisited edge back to visited vertex... 454 if ($This->_IsVisitedEdge($VertexID, $NeighborVertexID) || $This->_IsVisitedEdge($NeighborVertexID, $VertexID)) { 455 next NEIGHBOR; 456 } 457 # Check its depth... 458 if (exists $This->{DepthLimit}) { 459 if (($This->{VerticesDepth}{$VertexID} + 1) >= $This->{DepthLimit}) { 460 next NEIGHBOR; 461 } 462 } 463 464 # It's the edge final edge of a cycle in case $NeighborVertexID is already in the path; otherwise, it's 465 # part of the path from a different direction in a cycle or a left over vertex during Limit search. 466 # 467 if ($This->_IsCycleClosureEdge($VertexID, $NeighborVertexID)) { 468 if ($This->{AllowPathCycles}) { 469 $This->{CycleClosureVertices}{$NeighborVertexID} = 1; 470 $UnvisitedNeighborVertexID = $NeighborVertexID; 471 last NEIGHBOR; 472 } 473 } 474 else { 475 $UnvisitedNeighborVertexID = $NeighborVertexID; 476 last NEIGHBOR; 477 } 478 } 479 return $UnvisitedNeighborVertexID; 480 } 481 482 # Process visited vertex... 483 # 484 sub _ProcessVisitedVertex { 485 my($This, $VertexID, $PredecessorVertexID) = @_; 486 487 if (!exists $This->{VisitedVertices}{$VertexID}) { 488 # Add it to active vertices list... 489 push @{$This->{ActiveVertices}}, $VertexID; 490 491 # Mark vertex as visited vertex and take it out from the list of vertices to visit... 492 $This->{VisitedVertices}{$VertexID} = 1; 493 delete $This->{VerticesToVisit}{$VertexID}; 494 } 495 496 # Set up root vertex, predecessor vertex and distance from root... 497 if ($VertexID == $PredecessorVertexID) { 498 $This->{VerticesRoots}{$VertexID} = $VertexID; 499 500 $This->{VerticesPredecessors}{$VertexID} = $VertexID; 501 if (!exists $This->{VerticesSuccessors}{$VertexID}) { 502 @{$This->{VerticesSuccessors}{$VertexID}} = (); 503 } 504 505 $This->{VerticesDepth}{$VertexID} = 0; 506 507 if ($This->{TraversalMode} =~ /^Path$/i) { 508 $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); 509 } 510 } 511 else { 512 $This->{VerticesRoots}{$VertexID} = $This->{VerticesRoots}{$PredecessorVertexID}; 513 514 $This->{VerticesPredecessors}{$VertexID} = $PredecessorVertexID; 515 if (!exists $This->{VerticesSuccessors}{$PredecessorVertexID}) { 516 @{$This->{VerticesSuccessors}{$PredecessorVertexID}} = (); 517 } 518 push @{$This->{VerticesSuccessors}{$PredecessorVertexID}}, $VertexID; 519 520 if (!exists $This->{VerticesDepth}{$VertexID}) { 521 $This->{VerticesDepth}{$VertexID} = $This->{VerticesDepth}{$PredecessorVertexID} + 1; 522 } 523 524 if ($This->{TraversalMode} =~ /^Path$/i) { 525 $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); 526 $This->_ProcessVisitedEdge($PredecessorVertexID, $VertexID); 527 } 528 elsif ($This->{TraversalMode} =~ /^VertexNeighborhood$/i) { 529 $This->_ProcessVisitedEdge($PredecessorVertexID, $VertexID); 530 } 531 } 532 return $This; 533 } 534 535 # Process visited path... 536 # 537 sub _ProcessVisitedPath { 538 my($This, $VertexID, $PredecessorVertexID) = @_; 539 540 # Initialize VerticesPath... 541 if (!exists $This->{VerticesPaths}{$VertexID}) { 542 @{$This->{VerticesPaths}{$VertexID}} = (); 543 } 544 545 if ($VertexID == $PredecessorVertexID) { 546 # Starting of a path from root... 547 push @{$This->{VerticesPaths}{$VertexID}}, $VertexID; 548 } 549 else { 550 # Setup path for a vertex using path information from predecessor vertex... 551 if (exists $This->{CycleClosureVertices}{$PredecessorVertexID}) { 552 # Start of a new path from predecessor vertex... 553 push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexID}-${VertexID}"; 554 } 555 else { 556 my($PredecessorVertexPath); 557 for $PredecessorVertexPath (@{$This->{VerticesPaths}{$PredecessorVertexID}}) { 558 push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexPath}-${VertexID}"; 559 } 560 } 561 } 562 return $This; 563 } 564 565 # Process visited edge... 566 # 567 sub _ProcessVisitedEdge { 568 my($This, $VertexID1, $VertexID2) = @_; 569 570 if (!exists $This->{VisitedEdges}->{From}->{$VertexID1}) { 571 %{$This->{VisitedEdges}->{From}->{$VertexID1}} = (); 572 } 573 $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2} = $VertexID2; 574 575 if (!exists $This->{VisitedEdges}->{To}->{$VertexID2}) { 576 %{$This->{VisitedEdges}->{To}->{$VertexID2}} = (); 577 } 578 $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1} = $VertexID1; 579 580 return $This; 581 } 582 583 # Finished processing active vertex... 584 # 585 sub _ProcessFinishedVertex { 586 my($This, $VertexID) = @_; 587 588 if (!exists $This->{FinishedVertices}{$VertexID}) { 589 $This->{FinishedVertices}{$VertexID} = $VertexID; 590 # Add vertex to list of vertices found by traversal... 591 push @{$This->{Vertices}}, $VertexID; 592 } 593 594 # Any active vertices left... 595 if (!@{$This->{ActiveVertices}}) { 596 return $This; 597 } 598 599 # Take it off active vertices list... 600 if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { 601 # For DFS, it's last vertex in LIFO queue... 602 pop @{$This->{ActiveVertices}}; 603 } 604 elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { 605 # For BFS, it's first vertex in FIFO queue... 606 shift @{$This->{ActiveVertices}}; 607 } 608 return $This; 609 } 610 611 # Mark all other downstream neighbor vertices to be ignored from any further 612 # processing... 613 # 614 sub _IgnoreDownstreamNeighbors { 615 my($This, $VertexID, $PredecessorVertexID) = @_; 616 617 if (exists $This->{VerticesToVisit}{$VertexID}) { 618 # Mark vertex as visited vertex and take it out from the list of vertices to visit... 619 $This->{VisitedVertices}{$VertexID} = 1; 620 delete $This->{VerticesToVisit}{$VertexID}; 621 622 if (defined($PredecessorVertexID) && $This->{TraversalMode} =~ /^(Path|VertexNeighborhood)$/i) { 623 $This->_ProcessVisitedEdge($VertexID, $PredecessorVertexID); 624 } 625 } 626 my($NeighborVertexID, @NeighborsVertexIDs); 627 628 @NeighborsVertexIDs = (); 629 @NeighborsVertexIDs = $This->{Graph}->GetNeighbors($VertexID); 630 NEIGHBOR: for $NeighborVertexID (@NeighborsVertexIDs) { 631 if (!exists $This->{VerticesToVisit}{$NeighborVertexID}) { 632 # Avoid going back to predecessor vertex which has already been ignored... 633 next NEIGHBOR; 634 } 635 $This->_IgnoreDownstreamNeighbors($NeighborVertexID, $VertexID); 636 } 637 return $This; 638 } 639 640 # Is it a visited edge? 641 # 642 sub _IsVisitedEdge { 643 my($This, $VertexID1, $VertexID2) = @_; 644 645 if (exists $This->{VisitedEdges}->{From}->{$VertexID1}) { 646 if (exists $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2}) { 647 return 1; 648 } 649 } 650 elsif (exists $This->{VisitedEdges}->{To}->{$VertexID2}) { 651 if (exists $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1}) { 652 return 1; 653 } 654 } 655 return 0; 656 } 657 658 # Is it a cycle closure edge? 659 # 660 # Notes: 661 # . Presence of VertexID2 in DFS path traversed for VertexID1 make it a cycle 662 # closure edge... 663 # 664 sub _IsCycleClosureEdge { 665 my($This, $VertexID1, $VertexID2) = @_; 666 667 if (!exists $This->{VerticesPaths}{$VertexID1}) { 668 return 0; 669 } 670 my($Path); 671 for $Path (@{$This->{VerticesPaths}{$VertexID1}}) { 672 if (($Path =~ /-$VertexID2-/ || $Path =~ /^$VertexID2-/ || $Path =~ /-$VertexID2$/)) { 673 return 1; 674 } 675 } 676 return 0; 677 } 678 679 # Search paths starting from a specified vertex with no sharing of edges in paths traversed. 680 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 681 # completing the cycle. 682 # 683 sub PerformPathsSearch { 684 my($This, $StartVertexID, $AllowCycles) = @_; 685 686 # Make sure start vertex is defined... 687 if (!defined $StartVertexID) { 688 carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Start vertex must be specified..."; 689 return undef; 690 } 691 692 # Make sure start vertex is valid... 693 if (!$This->{Graph}->HasVertex($StartVertexID)) { 694 carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; 695 return undef; 696 } 697 698 if (!defined $AllowCycles) { 699 $AllowCycles = 1; 700 } 701 702 # Perform paths search... 703 return $This->_PerformPathsSearch("AllLengths", $StartVertexID, $AllowCycles); 704 } 705 706 # Search paths starting from a specified vertex with length upto a specified length 707 # with no sharing of edges in paths traversed... 708 # 709 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 710 # completing the cycle. 711 # 712 sub PerformPathsSearchWithLengthUpto { 713 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 714 715 return $This->_PerformPathsSearchWithLength("LengthUpto", $StartVertexID, $Length, $AllowCycles); 716 } 717 718 # Search paths starting from a specified vertex with specified length 719 # with no sharing of edges in paths traversed... 720 # 721 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 722 # completing the cycle. 723 # 724 sub PerformPathsSearchWithLength { 725 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 726 727 return $This->_PerformPathsSearchWithLength("Length", $StartVertexID, $Length, $AllowCycles); 728 } 729 730 731 # Search paths starting from a specified vertex with length upto a specified length 732 # with no sharing of edges in paths traversed... 733 # 734 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 735 # completing the cycle. 736 # 737 sub _PerformPathsSearchWithLength { 738 my($This, $Mode, $StartVertexID, $Length, $AllowCycles) = @_; 739 740 # Make sure both start vertex and length are defined... 741 if (!defined $StartVertexID) { 742 carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Start vertex must be specified..."; 743 return undef; 744 } 745 if (!defined $Length) { 746 carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Length must be specified..."; 747 return undef; 748 } 749 750 if (!defined $AllowCycles) { 751 $AllowCycles = 1; 752 } 753 754 # Make sure both start vertex and length are valid... 755 if (!$This->{Graph}->HasVertex($StartVertexID)) { 756 carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Vertex $StartVertexID doesn't exist..."; 757 return undef; 758 } 759 760 if ($Length < 1) { 761 carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; 762 return undef; 763 } 764 765 # Perform paths search... 766 return $This->_PerformPathsSearch($Mode, $StartVertexID, $AllowCycles, $Length); 767 } 768 769 # Search all paths starting from a specified vertex with sharing of edges in paths traversed... 770 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 771 # completing the cycle. 772 # 773 sub PerformAllPathsSearch { 774 my($This, $StartVertexID, $AllowCycles) = @_; 775 776 # Make sure start vertex is defined... 777 if (!defined $StartVertexID) { 778 carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Start vertex must be specified..."; 779 return undef; 780 } 781 782 # Make sure start vertex is valid... 783 if (!$This->{Graph}->HasVertex($StartVertexID)) { 784 carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; 785 return undef; 786 } 787 788 if (!defined $AllowCycles) { 789 $AllowCycles = 1; 790 } 791 792 # Perform paths search... 793 return $This->_PerformAllPathsSearch("AllLengths", $StartVertexID, $AllowCycles); 794 } 795 796 # Search all paths starting from a specified vertex with length upto a specified length with sharing of 797 # edges in paths traversed. 798 # 799 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 800 # completing the cycle. 801 # 802 sub PerformAllPathsSearchWithLengthUpto { 803 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 804 805 return $This->_PerformAllPathsSearchWithLength("LengthUpto", $StartVertexID, $Length, $AllowCycles); 806 } 807 808 # Search all paths starting from a specified vertex with specified length with sharing of 809 # edges in paths traversed. 810 # 811 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 812 # completing the cycle. 813 # 814 sub PerformAllPathsSearchWithLength { 815 my($This, $StartVertexID, $Length, $AllowCycles) = @_; 816 817 return $This->_PerformAllPathsSearchWithLength("Length", $StartVertexID, $Length, $AllowCycles); 818 } 819 820 # Search all paths starting from a specified vertex with length upto a specified length with sharing of 821 # edges in paths traversed. 822 # 823 # By default, cycles are included in paths. A path containing a cycle is terminated at a vertex 824 # completing the cycle. 825 # 826 sub _PerformAllPathsSearchWithLength { 827 my($This, $Mode, $StartVertexID, $Length, $AllowCycles) = @_; 828 829 # Make sure both start vertex and length are defined... 830 if (!defined $StartVertexID) { 831 carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Start vertex must be specified..."; 832 return undef; 833 } 834 if (!defined $Length) { 835 carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Length must be specified..."; 836 return undef; 837 } 838 839 if (!defined $AllowCycles) { 840 $AllowCycles = 1; 841 } 842 843 # Make sure both start vertex and length are valid... 844 if (!$This->{Graph}->HasVertex($StartVertexID)) { 845 carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Vertex $StartVertexID doesn't exist..."; 846 return undef; 847 } 848 849 if ($Length < 1) { 850 carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; 851 return undef; 852 } 853 854 # Perform paths search... 855 return $This->_PerformAllPathsSearch($Mode, $StartVertexID, $AllowCycles, $Length); 856 } 857 858 # Search paths between two vertices... 859 # 860 sub PerformPathsSearchBetween { 861 my($This, $StartVertexID, $EndVertexID) = @_; 862 863 # Make sure start and end vertices are defined... 864 if (!defined $StartVertexID) { 865 carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: Start vertex must be specified..."; 866 return undef; 867 } 868 if (!defined $EndVertexID) { 869 carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: EndVertex vertex must be specified..."; 870 return undef; 871 } 872 # Make sure start and end vertices are valid... 873 if (!$This->{Graph}->HasVertex($StartVertexID)) { 874 carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $StartVertexID doesn't exist..."; 875 return undef; 876 } 877 if (!$This->{Graph}->HasVertex($EndVertexID)) { 878 carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $EndVertexID doesn't exist..."; 879 return undef; 880 } 881 882 # Perform paths search... 883 return $This->_PerformPathsSearchBetween($StartVertexID, $EndVertexID); 884 } 885 886 # Search paths starting from root vertex with no sharing of edges... 887 # 888 # Notes: 889 # . Possible paths searche modes are: DFSPathsWithLimit, DFSPaths. And each 890 # of these modes supports any combination of two options: CommonEdges, Cycles. 891 # Default for CommonEdges - No; Cycles - No. 892 # 893 sub _PerformPathsSearch { 894 my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; 895 896 # Perform DFS path search... 897 898 $This->{TraversalMode} = 'Path'; 899 900 if ($Mode =~ /^(LengthUpto|Length)$/i) { 901 my($DepthLimit); 902 903 $DepthLimit = $Length - 1; 904 $This->{TraversalType} = 'DFSWithLimit'; 905 $This->{DepthLimit} = $DepthLimit; 906 } 907 else { 908 $This->{TraversalType} = 'DFS'; 909 } 910 if (defined $RootVertexID) { 911 $This->{RootVertex} = $RootVertexID; 912 } 913 914 $This->{AllowPathCycles} = $AllowCycles; 915 916 # Perform search... 917 $This->_TraverseGraph(); 918 919 # Make sure traversal did get the root vertex... 920 if (!exists $This->{VerticesDepth}{$RootVertexID}) { 921 return $This; 922 } 923 if ($Mode =~ /^Length$/i) { 924 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearchWithLength($Length); 925 } 926 else { 927 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); 928 } 929 930 return $This; 931 } 932 933 # Search all paths starting from root vertex with sharing of edges... 934 # 935 sub _PerformAllPathsSearch { 936 my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; 937 938 # Perform DFS path search... 939 940 $This->{TraversalMode} = 'AllPaths'; 941 942 if ($Mode =~ /^(LengthUpto|Length)$/i) { 943 my($DepthLimit); 944 945 $DepthLimit = $Length - 1; 946 $This->{TraversalType} = 'DFSWithLimit'; 947 $This->{DepthLimit} = $DepthLimit; 948 } 949 else { 950 $This->{TraversalType} = 'DFS'; 951 } 952 $This->{RootVertex} = $RootVertexID; 953 $This->{AllowPathCycles} = $AllowCycles; 954 955 # Traverse all paths search using DFS search... 956 $This->_TraverseAllPathsInGraph($Mode, $Length); 957 958 return $This; 959 } 960 961 # Travese all paths in graph starting from a specified root vertex... 962 # 963 sub _TraverseAllPathsInGraph { 964 my($This, $Mode, $Length) = @_; 965 966 if ($This->{TraversalMode} !~ /^AllPaths$/i) { 967 return $This; 968 } 969 my($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); 970 971 $CurrentVertexID = $This->{RootVertex}; 972 $PredecessorVertexID = $CurrentVertexID; 973 $CurrentDepth = 0; 974 $CurrentPath = "$CurrentVertexID"; 975 976 $This->_TraverseAllPaths($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); 977 978 if ($Mode =~ /^Length$/i) { 979 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearchWithLength($Length); 980 } 981 else { 982 push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); 983 } 984 985 return $This; 986 } 987 988 # Traverse and collect all paths recuresively.. 989 # 990 sub _TraverseAllPaths { 991 my($This, $CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath) = @_; 992 993 # Save path traversed for current vertex.. 994 if (!exists $This->{VerticesPaths}{$CurrentVertexID}) { 995 @{$This->{VerticesPaths}{$CurrentVertexID}} = (); 996 $This->{VerticesDepth}{$CurrentVertexID} = 0; 997 } 998 push @{$This->{VerticesPaths}{$CurrentVertexID}}, $CurrentPath; 999 $This->{VerticesDepth}{$CurrentVertexID} = $CurrentDepth; 1000 1001 $CurrentDepth++; 1002 if (exists $This->{DepthLimit}) { 1003 if ($CurrentDepth > $This->{DepthLimit}) { 1004 # Nothing more to do... 1005 return $This; 1006 } 1007 } 1008 my($NeighborVertexID, $NewPath); 1009 1010 NEIGHBOR: for $NeighborVertexID ($This->{Graph}->GetNeighbors($CurrentVertexID)) { 1011 if ($NeighborVertexID == $PredecessorVertexID) { 1012 next NEIGHBOR; 1013 } 1014 if ($This->_IsVertexInTraversedPath($NeighborVertexID, $CurrentPath)) { 1015 # It's a cycle... 1016 if ($This->{AllowPathCycles}) { 1017 $NewPath = "${CurrentPath}-${NeighborVertexID}"; 1018 if (!exists $This->{VerticesPaths}{$NeighborVertexID}) { 1019 @{$This->{VerticesPaths}{$NeighborVertexID}} = (); 1020 } 1021 push @{$This->{VerticesPaths}{$NeighborVertexID}}, $NewPath; 1022 } 1023 next NEIGHBOR; 1024 } 1025 $NewPath = "${CurrentPath}-${NeighborVertexID}"; 1026 $This->_TraverseAllPaths($NeighborVertexID, $CurrentVertexID, $CurrentDepth, $NewPath); 1027 } 1028 return $This; 1029 } 1030 1031 # Is vertex already in traversed path? 1032 # 1033 sub _IsVertexInTraversedPath { 1034 my($This, $VertexID, $Path) = @_; 1035 1036 return ($Path =~ /-$VertexID-/ || $Path =~ /^$VertexID-/ || $Path =~ /-$VertexID$/) ? 1 : 0; 1037 } 1038 1039 # Collect all paths traversed during Path TraversalMode and sort 'em in 1040 # ascending order of lengths 1041 # 1042 sub _CollectPathsTraversedDuringPathsSearch { 1043 my($This) = @_; 1044 my($VertexID, @Paths, @SortedPaths); 1045 1046 @Paths = (); @SortedPaths = (); 1047 1048 # Create path objects from path vertex strings... 1049 for $VertexID (keys %{$This->{VerticesPaths}}) { 1050 push @Paths, map { new Graph::Path(split /-/, $_) } @{$This->{VerticesPaths}{$VertexID}}; 1051 } 1052 1053 # Sort paths in ascending order of lengths... 1054 push @SortedPaths, sort { $a->GetLength() <=> $b->GetLength() } @Paths; 1055 1056 return @SortedPaths; 1057 } 1058 1059 # Collect paths traversed during Path TraversalMode with specific length... 1060 # 1061 sub _CollectPathsTraversedDuringPathsSearchWithLength { 1062 my($This, $Length) = @_; 1063 my($VertexID, $Depth, $PathString, @VertexIDs, @Paths); 1064 1065 @Paths = (); 1066 $Depth = $Length - 1; 1067 1068 # Create path objects from path vertex strings... 1069 VERTEXID: for $VertexID (keys %{$This->{VerticesPaths}}) { 1070 if ($This->{VerticesDepth}{$VertexID} != $Depth) { 1071 next VERTEXID; 1072 } 1073 # For vertices involved in cycles, the path might also contain some shorter paths. So check 1074 # the lengths before its collection... 1075 PATHSTRING: for $PathString (@{$This->{VerticesPaths}{$VertexID}}) { 1076 @VertexIDs = split /-/, $PathString; 1077 if ($Length != @VertexIDs) { 1078 next PATHSTRING; 1079 } 1080 push @Paths, new Graph::Path(@VertexIDs); 1081 } 1082 } 1083 return @Paths; 1084 } 1085 1086 # Collect paths traversed during Vertex TraversalMode... 1087 # 1088 sub _CollectPathsTraversedDuringVertexSearch { 1089 my($This, $RootVertexID) = @_; 1090 my($Depth, @Paths, @VerticesAtDepth); 1091 @Paths = (); 1092 1093 # Get vertices at specific depths... 1094 @VerticesAtDepth = (); 1095 @VerticesAtDepth = $This->_CollectVerticesAtSpecificDepths(); 1096 if (!@VerticesAtDepth) { 1097 return @Paths; 1098 } 1099 1100 # Make sure search found only one root vertex and it corresponds to 1101 # what was specified... 1102 $Depth = 0; 1103 if ((@{$VerticesAtDepth[$Depth]} > 1) || ($VerticesAtDepth[$Depth][0] != $RootVertexID)) { 1104 carp "Warning: ${ClassName}->_PerformPathsSearch: No paths found: Root vertex, $VerticesAtDepth[$Depth][0], identified by paths traversal doen't match specified root vertex $RootVertexID..."; 1105 return @Paths; 1106 } 1107 1108 # Setup root vertex at depth 0. And set its path... 1109 my($Path, $VertexID, $SuccessorVertexID, @VertexIDs, %PathAtVertex); 1110 %PathAtVertex = (); 1111 $PathAtVertex{$RootVertexID} = new Graph::Path($RootVertexID); 1112 1113 for $Depth (0 .. $#VerticesAtDepth) { 1114 # Go over all vertices at current depth... 1115 VERTEX: for $VertexID (@{$VerticesAtDepth[$Depth]}) { 1116 if (!exists $This->{VerticesSuccessors}{$VertexID}) { 1117 next VERTEX; 1118 } 1119 # Get vertices for current path... 1120 @VertexIDs = (); 1121 push @VertexIDs, $PathAtVertex{$VertexID}->GetVertices; 1122 1123 # Expand path to successor vertex found during traversal... 1124 for $SuccessorVertexID (@{$This->{VerticesSuccessors}{$VertexID}}) { 1125 $Path = new Graph::Path(@VertexIDs); 1126 $Path->AddVertex($SuccessorVertexID); 1127 $PathAtVertex{$SuccessorVertexID} = $Path; 1128 } 1129 } 1130 } 1131 # Sort paths in ascending order of lengths... 1132 push @Paths, sort { $a->GetLength() <=> $b->GetLength() } values %PathAtVertex; 1133 1134 return @Paths; 1135 } 1136 1137 # Collect vertices at specific depths. Depth values start from 0... 1138 # 1139 sub _CollectVerticesAtSpecificDepths { 1140 my($This) = @_; 1141 my($VertexID, $Depth, @VerticesAtDepth); 1142 1143 @VerticesAtDepth = (); 1144 while (($VertexID, $Depth) = each %{$This->{VerticesDepth}}) { 1145 push @{$VerticesAtDepth[$Depth]}, $VertexID; 1146 } 1147 return @VerticesAtDepth; 1148 } 1149 1150 # Collect vertices, along with their successors, at specific depths and return a list containing references to 1151 # lists with first value corresponding to vertex ID and second value a reference to a list containing 1152 # its successors. 1153 # 1154 # Depth values start from 0... 1155 # 1156 sub _CollectVerticesWithSuccessorsAtSpecificDepths { 1157 my($This) = @_; 1158 my($VertexID, $Depth, @VerticesWithSuccessorsAtDepth); 1159 1160 @VerticesWithSuccessorsAtDepth = (); 1161 while (($VertexID, $Depth) = each %{$This->{VerticesDepth}}) { 1162 my(@VertexWithSuccessors, @VertexSuccessors); 1163 1164 @VertexWithSuccessors = (); @VertexSuccessors = (); 1165 if (exists $This->{VerticesSuccessors}{$VertexID}) { 1166 push @VertexSuccessors, @{$This->{VerticesSuccessors}{$VertexID}}; 1167 } 1168 push @VertexWithSuccessors, ($VertexID, \@VertexSuccessors); 1169 # Multiple entries for a vertex and its successors could be present at a specific depth... 1170 push @{$VerticesWithSuccessorsAtDepth[$Depth]}, \@VertexWithSuccessors; 1171 } 1172 return @VerticesWithSuccessorsAtDepth; 1173 } 1174 1175 # Search paths between two vertices... 1176 # 1177 sub _PerformPathsSearchBetween { 1178 my($This, $RootVertexID, $TargetVertexID) = @_; 1179 my($DepthLimit); 1180 1181 # Perform a targeted DFS search... 1182 $DepthLimit = undef; 1183 $This->_PerformVertexSearch("DFS", $RootVertexID, $DepthLimit, $TargetVertexID); 1184 1185 my($Path); 1186 $Path = $This->_CollectPathBetween($RootVertexID, $TargetVertexID); 1187 1188 if (defined $Path) { 1189 push @{$This->{Paths}}, $Path; 1190 } 1191 return $This; 1192 } 1193 1194 # Collect path between root and target vertex after the search... 1195 # 1196 sub _CollectPathBetween { 1197 my($This, $RootVertexID, $TargetVertexID) = @_; 1198 1199 # Does a path from root to target vertex exist? 1200 if (!(exists($This->{VerticesRoots}{$TargetVertexID}) && ($This->{VerticesRoots}{$TargetVertexID} == $RootVertexID))) { 1201 return undef; 1202 } 1203 1204 # Add target vertex ID path vertices... 1205 my($VertexID, $Path, @VertexIDs); 1206 @VertexIDs = (); 1207 $VertexID = $TargetVertexID; 1208 push @VertexIDs, $VertexID; 1209 1210 # Backtrack to root vertex ID... 1211 while ($This->{VerticesPredecessors}{$VertexID} != $VertexID) { 1212 $VertexID = $This->{VerticesPredecessors}{$VertexID}; 1213 push @VertexIDs, $VertexID; 1214 } 1215 1216 # Create path from target to root and reverse it... 1217 $Path = new Graph::Path(@VertexIDs); 1218 $Path->Reverse(); 1219 1220 return $Path; 1221 } 1222 1223 # Search vertices around specified root vertex with in specific neighborhood radius... 1224 # 1225 sub PerformNeighborhoodVerticesSearchWithRadiusUpto { 1226 my($This, $StartVertexID, $Radius) = @_; 1227 1228 # Make sure both start vertex and radius are defined... 1229 if (!defined $StartVertexID) { 1230 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Start vertex must be specified..."; 1231 return undef; 1232 } 1233 if (!defined $Radius) { 1234 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Radius must be specified..."; 1235 return undef; 1236 } 1237 1238 # Make sure both start vertex and length are valid... 1239 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1240 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1241 return undef; 1242 } 1243 if ($Radius < 0) { 1244 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Specified radius, $Radius, must be a positive integer..."; 1245 return undef; 1246 } 1247 1248 # Perform vertices search... 1249 return $This->_PerformNeighborhoodVerticesSearch("RadiusUpto", $StartVertexID, $Radius); 1250 } 1251 1252 # Search vertices around specified root vertex... 1253 # 1254 sub PerformNeighborhoodVerticesSearch { 1255 my($This, $StartVertexID) = @_; 1256 1257 # Make sure start vertex is defined... 1258 if (!defined $StartVertexID) { 1259 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Start vertex must be specified..."; 1260 return undef; 1261 } 1262 1263 # Make sure start vertex is valid... 1264 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1265 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1266 return undef; 1267 } 1268 # Perform vertices search... 1269 return $This->_PerformNeighborhoodVerticesSearch("AllRadii", $StartVertexID); 1270 } 1271 1272 # Search vertices around specified root vertex with in specific neighborhood radius along with 1273 # identification of successors of each vertex found during the search... 1274 # 1275 sub PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto { 1276 my($This, $StartVertexID, $Radius) = @_; 1277 1278 # Make sure both start vertex and radius are defined... 1279 if (!defined $StartVertexID) { 1280 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Start vertex must be specified..."; 1281 return undef; 1282 } 1283 if (!defined $Radius) { 1284 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Radius must be specified..."; 1285 return undef; 1286 } 1287 1288 # Make sure both start vertex and length are valid... 1289 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1290 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1291 return undef; 1292 } 1293 if ($Radius < 0) { 1294 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Specified radius, $Radius, must be a positive integer..."; 1295 return undef; 1296 } 1297 1298 # Perform vertices search... 1299 return $This->_PerformNeighborhoodVerticesSearch("WithSuccessorsAndRadiusUpto", $StartVertexID, $Radius); 1300 } 1301 1302 # Search vertices around specified root vertex along with identification of 1303 # successors of each vertex found during the search... 1304 # 1305 sub PerformNeighborhoodVerticesSearchWithSuccessors { 1306 my($This, $StartVertexID) = @_; 1307 1308 # Make sure start vertex is defined... 1309 if (!defined $StartVertexID) { 1310 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessors: No vertices search performed: Start vertex must be specified..."; 1311 return undef; 1312 } 1313 1314 # Make sure start vertex is valid... 1315 if (!$This->{Graph}->HasVertex($StartVertexID)) { 1316 carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessors: No vertices search performed: Vertex $StartVertexID doesn't exist..."; 1317 return undef; 1318 } 1319 # Perform vertices search... 1320 return $This->_PerformNeighborhoodVerticesSearch("WithSuccessorsAndAllRadii", $StartVertexID); 1321 } 1322 1323 # Search vertices at successive neighborhood radii levels... 1324 # 1325 sub _PerformNeighborhoodVerticesSearch { 1326 my($This, $Mode, $RootVertexID, $Radius) = @_; 1327 my($DepthLimit, $AllowCycles); 1328 1329 $DepthLimit = defined $Radius ? $Radius : undef; 1330 $AllowCycles = undef; 1331 1332 # Perform BFS search... 1333 if ($Mode =~ /^RadiusUpto$/i) { 1334 $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit); 1335 } 1336 elsif ($Mode =~ /^(AllRadii)$/i) { 1337 $This->_PerformVertexNeighborhoodSearch("BFS", $RootVertexID); 1338 } 1339 elsif ($Mode =~ /^WithSuccessorsAndRadiusUpto$/i) { 1340 $AllowCycles = 1; 1341 $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit, $AllowCycles); 1342 } 1343 elsif ($Mode =~ /^WithSuccessorsAndAllRadii$/i) { 1344 $AllowCycles = 1; 1345 $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit, $AllowCycles); 1346 } 1347 1348 # Make sure traversal did get the root vertex... 1349 if (!exists $This->{VerticesDepth}{$RootVertexID}) { 1350 return $This; 1351 } 1352 1353 if ($Mode =~ /^(RadiusUpto|AllRadii)$/i) { 1354 push @{$This->{VerticesNeighborhoods}}, $This->_CollectVerticesAtSpecificDepths(); 1355 } 1356 elsif ($Mode =~ /^(WithSuccessorsAndRadiusUpto|WithSuccessorsAndAllRadii)$/i) { 1357 push @{$This->{VerticesNeighborhoodsWithSuccessors}}, $This->_CollectVerticesWithSuccessorsAtSpecificDepths(); 1358 } 1359 1360 return $This; 1361 } 1362 1363 # Perform appropriate vertex search... 1364 # 1365 sub _PerformVertexNeighborhoodSearch { 1366 my($This, $SearchType, $RootVertexID, $DepthLimit, $AllowCycles) = @_; 1367 1368 # Setup search... 1369 $This->{TraversalMode} = 'VertexNeighborhood'; 1370 $This->{TraversalType} = $SearchType; 1371 1372 if (defined $RootVertexID) { 1373 $This->{RootVertex} = $RootVertexID; 1374 } 1375 if (defined $DepthLimit) { 1376 $This->{DepthLimit} = $DepthLimit; 1377 } 1378 if (defined $AllowCycles) { 1379 $This->{AllowVertexCycles} = $AllowCycles; 1380 } 1381 1382 # Perform search... 1383 return $This->_TraverseGraph(); 1384 } 1385 1386 # Get orderded list of vertices after DFS/BFS search... 1387 # 1388 sub GetVertices { 1389 my($This) = @_; 1390 1391 return wantarray ? @{$This->{Vertices}} : scalar @{$This->{Vertices}}; 1392 } 1393 1394 # Get a hash list containing vertex and root vertex as key/value pair for all vertices 1395 # ordered using DFS/BFS search available via GetVertices method... 1396 # 1397 sub GetVerticesRoots { 1398 my($This) = @_; 1399 1400 return %{$This->{VerticesRoots}}; 1401 } 1402 1403 # Get a list containing lists of vertices in connected components of graph after DFS/BFS 1404 # search... 1405 # 1406 # Note: 1407 # . List is sorted in descending order of number of vertices in each connected component. 1408 # 1409 sub GetConnectedComponentsVertices { 1410 my($This) = @_; 1411 my($VertexID, $VertexRoot, @ConnectedVertices, %VerticesAtRoot); 1412 1413 @ConnectedVertices = (); 1414 %VerticesAtRoot = (); 1415 for $VertexID (@{$This->{Vertices}}) { 1416 $VertexRoot = $This->{VerticesRoots}{$VertexID}; 1417 if (!exists $VerticesAtRoot{$VertexRoot}) { 1418 @{$VerticesAtRoot{$VertexRoot}} = (); 1419 } 1420 push @{$VerticesAtRoot{$VertexRoot}}, $VertexID; 1421 } 1422 push @ConnectedVertices, sort { @{$b} <=> @{$a} } values %VerticesAtRoot; 1423 1424 return wantarray ? @ConnectedVertices : scalar @ConnectedVertices; 1425 } 1426 1427 # Get predecessor vertices... 1428 # 1429 sub GetVerticesPredecessors { 1430 my($This) = @_; 1431 1432 return %{$This->{VerticesPredecessors}}; 1433 } 1434 1435 # Get a hash list containing vertex and depth from root vertex as key/value pair for all vertices 1436 # ordered using DFS/BFS search available via GetVertices method... 1437 # 1438 sub GetVerticesDepth { 1439 my($This) = @_; 1440 1441 return %{$This->{VerticesDepth}}; 1442 } 1443 1444 # Get paths found during paths search... 1445 # 1446 sub GetPaths { 1447 my($This) = @_; 1448 1449 return wantarray ? @{$This->{Paths}} : scalar @{$This->{Paths}}; 1450 } 1451 1452 # Get vertices collected at various neighborhood radii... 1453 # 1454 sub GetVerticesNeighborhoods { 1455 my($This) = @_; 1456 1457 return wantarray ? @{$This->{VerticesNeighborhoods}} : scalar @{$This->{VerticesNeighborhoods}}; 1458 } 1459 1460 # Get vertices, along with their successor vertices, collected at various neighborhood radii as 1461 # a list containing references to lists with first value corresponding to vertex ID and second value 1462 # a reference to a list containing its successors. 1463 # 1464 sub GetVerticesNeighborhoodsWithSuccessors { 1465 my($This) = @_; 1466 1467 return wantarray ? @{$This->{VerticesNeighborhoodsWithSuccessors}} : scalar @{$This->{VerticesNeighborhoodsWithSuccessors}}; 1468 } 1469 1470 # Return a string containg data for PathsTraversal object... 1471 sub StringifyPathsTraversal { 1472 my($This) = @_; 1473 my($PathsTraversalString); 1474 1475 $PathsTraversalString = "PathsTraversalMode: " . $This->{TraversalMode}; 1476 $PathsTraversalString .= "; PathsTraversalType: " . $This->{TraversalType}; 1477 1478 # Vertices ordered by traversal... 1479 $PathsTraversalString .= "; Vertices: " . join(' ', @{$This->{Vertices}}); 1480 1481 # Stringify depths of vertices... 1482 $PathsTraversalString .= "; " . $This->StringifyVerticesDepth(); 1483 1484 # Stringify roots of vertices... 1485 $PathsTraversalString .= "; " . $This->StringifyVerticesRoots(); 1486 1487 # Stringify predecessor of vertices... 1488 $PathsTraversalString .= "; " . $This->StringifyVerticesPredecessors(); 1489 1490 # Stringify successor vertices... 1491 $PathsTraversalString .= "; " . $This->StringifyVerticesSuccessors(); 1492 1493 # Stringify paths... 1494 $PathsTraversalString .= "; " . $This->StringifyPaths(); 1495 1496 # Stringify vertices neighborhoods... 1497 $PathsTraversalString .= "; " . $This->StringifyVerticesNeighborhoods(); 1498 1499 # Stringify vertices neighborhoods with successors... 1500 $PathsTraversalString .= "; " . $This->StringifyVerticesNeighborhoodsWithSuccessors(); 1501 1502 return $PathsTraversalString; 1503 } 1504 1505 # Stringify vertices depth... 1506 # 1507 sub StringifyVerticesDepth { 1508 my($This) = @_; 1509 my($VertexID, $VertexDepth, $DepthString); 1510 1511 if (!@{$This->{Vertices}}) { 1512 $DepthString = "<Vertex-Depth>: None"; 1513 return $DepthString; 1514 } 1515 1516 $DepthString = "<Vertex-Depth>: "; 1517 for $VertexID (@{$This->{Vertices}}) { 1518 $VertexDepth = $This->{VerticesDepth}{$VertexID}; 1519 $DepthString .= " <$VertexID-$VertexDepth>"; 1520 } 1521 return $DepthString; 1522 } 1523 1524 # Stringify roots of vertices... 1525 # 1526 sub StringifyVerticesRoots { 1527 my($This) = @_; 1528 my($VertexID, $RootVertexID, $RootsString); 1529 1530 if (!@{$This->{Vertices}}) { 1531 $RootsString = "<Vertex-RootVertex>: None"; 1532 return $RootsString; 1533 } 1534 1535 $RootsString = "<Vertex-RootVertex>: "; 1536 for $VertexID (@{$This->{Vertices}}) { 1537 $RootVertexID = $This->{VerticesRoots}{$VertexID}; 1538 $RootsString .= " <$VertexID-$RootVertexID>"; 1539 } 1540 return $RootsString; 1541 } 1542 1543 # Stringify predecessor of vertices... 1544 # 1545 sub StringifyVerticesPredecessors { 1546 my($This) = @_; 1547 my($VertexID, $PredecessorVertexID, $PredecessorString); 1548 1549 if (!@{$This->{Vertices}}) { 1550 $PredecessorString = "<Vertex-PredecessorVertex>: None"; 1551 return $PredecessorString; 1552 } 1553 1554 $PredecessorString = "<Vertex-PredecessorVertex>: "; 1555 for $VertexID (@{$This->{Vertices}}) { 1556 $PredecessorVertexID = $This->{VerticesPredecessors}{$VertexID}; 1557 $PredecessorString .= " <$VertexID-$PredecessorVertexID>"; 1558 } 1559 return $PredecessorString; 1560 } 1561 1562 # Stringify successor vertices... 1563 # 1564 sub StringifyVerticesSuccessors { 1565 my($This) = @_; 1566 my($VertexID, $SuccessorString, $VerticesSuccessorsString); 1567 1568 if (!@{$This->{Vertices}}) { 1569 $SuccessorString = "<Vertex-VerticesSuccessorsList>: None"; 1570 return $SuccessorString; 1571 } 1572 1573 $SuccessorString = "<Vertex-VerticesSuccessorsList>: "; 1574 for $VertexID (@{$This->{Vertices}}) { 1575 if (exists($This->{VerticesSuccessors}{$VertexID}) && @{$This->{VerticesSuccessors}{$VertexID}}) { 1576 $VerticesSuccessorsString = join(',', @{$This->{VerticesSuccessors}{$VertexID}}); 1577 } 1578 else { 1579 $VerticesSuccessorsString = "None"; 1580 } 1581 $SuccessorString .= " <$VertexID-$VerticesSuccessorsString>"; 1582 } 1583 return $SuccessorString; 1584 } 1585 1586 # Strinigify paths... 1587 # 1588 sub StringifyPaths { 1589 my($This) = @_; 1590 my($PathsString, $Path); 1591 1592 if (!@{$This->{Paths}}) { 1593 $PathsString = "Paths: None"; 1594 return $PathsString; 1595 } 1596 1597 my($FirstPath); 1598 $PathsString = "Paths: "; 1599 $FirstPath = 1; 1600 for $Path (@{$This->{Paths}}) { 1601 if ($FirstPath) { 1602 $FirstPath = 0; 1603 } 1604 else { 1605 $PathsString .= " "; 1606 } 1607 $PathsString .= "<" . join('-', $Path->GetVertices()) . ">"; 1608 } 1609 return $PathsString; 1610 } 1611 1612 # Strinigify vertices neighborhoods... 1613 # 1614 sub StringifyVerticesNeighborhoods { 1615 my($This) = @_; 1616 my($NeighborhoodsString, $NeighborhoodVerticesString, $Radius); 1617 1618 if (!@{$This->{VerticesNeighborhoods}}) { 1619 $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVerticesList>: None"; 1620 return $NeighborhoodsString; 1621 } 1622 $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVerticesList>:"; 1623 for $Radius (0 .. $#{$This->{VerticesNeighborhoods}}) { 1624 $NeighborhoodVerticesString = join(',', @{$This->{VerticesNeighborhoods}[$Radius]}); 1625 $NeighborhoodsString .= " <$Radius-$NeighborhoodVerticesString>"; 1626 } 1627 1628 return $NeighborhoodsString; 1629 } 1630 1631 # Strinigify vertices neighborhoods... 1632 # 1633 sub StringifyVerticesNeighborhoodsWithSuccessors { 1634 my($This) = @_; 1635 my($NeighborhoodsString, $NeighborhoodVertexSuccessorsString, $Radius, $NeighborhoodVertericesWithSuccessorsRef, $NeighborhoodVertexWithSuccessorsRef, $VertexID, $NeighborhoodVertexSuccessorsRef); 1636 1637 if (!@{$This->{VerticesNeighborhoodsWithSuccessors}}) { 1638 $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVertex-NeighborhoodVerticeSuccessorsList>: None"; 1639 return $NeighborhoodsString; 1640 } 1641 $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVertex-NeighborhoodVerticeSuccessorsList>: None"; 1642 1643 $Radius = 0; 1644 for $NeighborhoodVertericesWithSuccessorsRef (@{$This->{VerticesNeighborhoodsWithSuccessors}}) { 1645 for $NeighborhoodVertexWithSuccessorsRef (@{$NeighborhoodVertericesWithSuccessorsRef}) { 1646 ($VertexID, $NeighborhoodVertexSuccessorsRef) = @{$NeighborhoodVertexWithSuccessorsRef}; 1647 $NeighborhoodVertexSuccessorsString = 'None'; 1648 if (@{$NeighborhoodVertexSuccessorsRef}) { 1649 $NeighborhoodVertexSuccessorsString = join(',', @{$NeighborhoodVertexSuccessorsRef}); 1650 } 1651 $NeighborhoodsString .= " <$Radius-$VertexID-$NeighborhoodVertexSuccessorsString>"; 1652 } 1653 $Radius++; 1654 } 1655 return $NeighborhoodsString; 1656 } 1657 1658 # Return a reference to new paths traversal object... 1659 sub Copy { 1660 my($This) = @_; 1661 my($NewPathsTraversal); 1662 1663 $NewPathsTraversal = Storable::dclone($This); 1664 1665 return $NewPathsTraversal; 1666 } 1667