Mercurial > repos > deepakjadmin > mayatool3_test2
diff lib/Graph/PathsTraversal.pm @ 0:4816e4a8ae95 draft default tip
Uploaded
author | deepakjadmin |
---|---|
date | Wed, 20 Jan 2016 09:23:18 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/Graph/PathsTraversal.pm Wed Jan 20 09:23:18 2016 -0500 @@ -0,0 +1,2002 @@ +package Graph::PathsTraversal; +# +# $RCSfile: PathsTraversal.pm,v $ +# $Date: 2015/02/28 20:49:06 $ +# $Revision: 1.29 $ +# +# Author: Manish Sud <msud@san.rr.com> +# +# Copyright (C) 2015 Manish Sud. All rights reserved. +# +# This file is part of MayaChemTools. +# +# MayaChemTools is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# MayaChemTools is distributed in the hope that it will be useful, but without +# any warranty; without even the implied warranty of merchantability of fitness +# for a particular purpose. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or +# write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, +# Boston, MA, 02111-1307, USA. +# + +use strict; +use Carp; +use Exporter; +use Graph; +use Graph::Path; + +use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + +@ISA = qw(Exporter); +@EXPORT = qw(); +@EXPORT_OK = qw(); + +%EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); + +# Setup class variables... +my($ClassName); +_InitializeClass(); + +# Overload Perl functions... +use overload '""' => 'StringifyPathsTraversal'; + +# Class constructor... +sub new { + my($Class, $Graph) = @_; + + # Initialize object... + my $This = {}; + bless $This, ref($Class) || $Class; + $This->_InitializePathsTraversal($Graph); + + return $This; +} + +# Initialize object data... +sub _InitializePathsTraversal { + my($This, $Graph) = @_; + + # Graph object... + $This->{Graph} = $Graph; + + # Traversal mode: Vertex or Path + $This->{TraversalMode} = ''; + + # Traversal type: DFS, DFSWithLimit, BFS, BFSWithLimit... + $This->{TraversalType} = ''; + + # For finding root vertex and controlling search... + my(@VertexIDs); + @VertexIDs = $This->{Graph}->GetVertices(); + %{$This->{VerticesToVisit}} = (); + @{$This->{VerticesToVisit}}{ @VertexIDs } = @VertexIDs; + + # Root vertex of all visited vertices... + %{$This->{VerticesRoots}} = (); + + # Visited vertices... + %{$This->{VisitedVertices}} = (); + + # Finished vertices... + %{$This->{FinishedVertices}} = (); + + # List of active vertices during DFS/BFS search... + @{$This->{ActiveVertices}} = (); + + # List of ordered vertices traversed during DFS/BFS search... + @{$This->{Vertices}} = (); + + # Vertex neighbors during traversal... + %{$This->{VerticesNeighbors}} = (); + + # Vertices depth from root... + %{$This->{VerticesDepth}} = (); + + # Predecessor of each vertex during vertex traversal. For root vertex, it's root itself... + %{$This->{VerticesPredecessors}} = (); + + # Successors of each vertex during vertex traversal... + %{$This->{VerticesSuccessors}} = (); + + # Vertices at different neighborhood levels during vertex traversal... + @{$This->{VerticesNeighborhoods}} = (); + + # Vertices, along with their successors, at different neighborhood levels during vertex traversal... + @{$This->{VerticesNeighborhoodsWithSuccessors}} = (); + + # Visited edges during Path TraversalMode... + %{$This->{VisitedEdges}} = (); + %{$This->{VisitedEdges}->{From}} = (); + %{$This->{VisitedEdges}->{To}} = (); + + # Vertex path during Path TraversalMode... + %{$This->{VerticesPaths}} = (); + + # Allow cycles in paths during VertexNeighborhood TraversalMode. By default, cycles are not allowed + # during vertex traversal: a vertex is only visited once during BFS search for neighborhoods. For + # neighborhood vertices search during successors identification, vertex cycles are explicity allowed + # to indentify all successors. + $This->{AllowVertexCycles} = 0; + + # Allow cycles in paths during Path TraversalMode... + $This->{AllowPathCycles} = 1; + + # Cycle closure vertices during Path TraversalMode... + %{$This->{CycleClosureVertices}} = (); + + # Paths traversed during search... + @{$This->{Paths}} = (); + + return $This; +} + +# Initialize class ... +sub _InitializeClass { + #Class name... + $ClassName = __PACKAGE__; +} + +# Perform a depth first search (DFS)... +# +sub PerformDepthFirstSearch { + my($This, $RootVertexID) = @_; + + if (defined $RootVertexID) { + if (!$This->{Graph}->HasVertex($RootVertexID)) { + carp "Warning: ${ClassName}->PerformDepthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; + return undef; + } + } + return $This->_PerformVertexSearch("DFS", $RootVertexID); +} + +# Perform a depth first search (DFS) with limit on depth... +# +sub PerformDepthFirstSearchWithLimit { + my($This, $DepthLimit, $RootVertexID) = @_; + + if (!defined $DepthLimit) { + carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; + return undef; + } + if ($DepthLimit < 0) { + carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; + return undef; + } + if (defined $RootVertexID) { + if (!$This->{Graph}->HasVertex($RootVertexID)) { + carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; + return undef; + } + } + return $This->_PerformVertexSearch("DFSWithLimit", $RootVertexID, $DepthLimit); +} + +# Perform a breadth first search (BFS)... +# +sub PerformBreadthFirstSearch { + my($This, $RootVertexID) = @_; + + if (defined $RootVertexID) { + if (!$This->{Graph}->HasVertex($RootVertexID)) { + carp "Warning: ${ClassName}->PerformBreadthFirstSearch: No search performed: Vertex $RootVertexID doesn't exist..."; + return undef; + } + } + return $This->_PerformVertexSearch("BFS", $RootVertexID); +} + +# Perform a breadth first search (BFS) with limit... +# +sub PerformBreadthFirstSearchWithLimit { + my($This, $DepthLimit, $RootVertexID) = @_; + + if (!defined $DepthLimit) { + carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Depth limit must be specified..."; + return undef; + } + if ($DepthLimit < 0) { + carp "Warning: ${ClassName}->PerformBreadthFirstSearchWithLimit: No search performed: Specified depth limit, $DepthLimit, must be a positive integer..."; + return undef; + } + if (defined $RootVertexID) { + if (!$This->{Graph}->HasVertex($RootVertexID)) { + carp "Warning: ${ClassName}->PerformDepthFirstSearchWithLimit: No search performed: Vertex $RootVertexID doesn't exist..."; + return undef; + } + } + return $This->_PerformVertexSearch("BFSWithLimit", $RootVertexID, $DepthLimit); +} + +# Perform appropriate vertex search... +# +sub _PerformVertexSearch { + my($This, $SearchType, $RootVertexID, $DepthLimit, $TargetVertexID) = @_; + + # Setup search... + $This->{TraversalMode} = 'Vertex'; + $This->{TraversalType} = $SearchType; + + if (defined $RootVertexID) { + $This->{RootVertex} = $RootVertexID; + } + if (defined $DepthLimit) { + $This->{DepthLimit} = $DepthLimit; + } + if (defined $TargetVertexID) { + $This->{TargetVertex} = $TargetVertexID; + } + + # Perform search... + return $This->_TraverseGraph(); +} + +# Perform DFS or BFS traversal with or without any limits... +# +sub _TraverseGraph { + my($This) = @_; + my($ProcessingVertices, $CurrentVertexID, $NeighborVertexID, $VertexID); + + if ($This->{TraversalMode} !~ /^(Vertex|Path|VertexNeighborhood)$/i) { + return $This; + } + + $ProcessingVertices = 1; + + VERTICES: while ($ProcessingVertices) { + # Set root vertex... + if (!@{$This->{ActiveVertices}}) { + my($RootVertexID); + + $RootVertexID = $This->_GetRootVertex(); + if (!defined $RootVertexID) { + $ProcessingVertices = 0; next VERTICES; + } + $This->_ProcessVisitedVertex($RootVertexID, $RootVertexID); + } + + # Get current active vertex... + $CurrentVertexID = $This->_GetActiveVertex(); + if (!defined $CurrentVertexID) { + $ProcessingVertices = 0; next VERTICES; + } + + # Get next available neighbor of current vertex... + # + $NeighborVertexID = $This->_GetNeighborVertex($CurrentVertexID); + + # Process neighbor or current vertex... + if (defined $NeighborVertexID) { + $This->_ProcessVisitedVertex($NeighborVertexID, $CurrentVertexID); + } + else { + # Finished with all neighbors for current vertex... + $This->_ProcessFinishedVertex($CurrentVertexID); + } + } + return $This; +} + +# Get root vertex to start the search... +# +# Notes: +# . User specification of root vertex forces traversal in a specific connected component +# of graph; To traverse find all connected components, perform traversal without specification of +# a root vertex. +# +sub _GetRootVertex { + my($This) = @_; + my($RootVertexID); + + # Check for specified root vertex and constrain traversal to specific connected + # component by setting root limit... + if (exists $This->{RootVertex}) { + $RootVertexID = $This->{RootVertex}; + delete $This->{RootVertex}; + $This->{RootVertexSpecified} = 1; + + return $RootVertexID; + } + # Traversal limited to connected component containing specified root vertex... + if (exists $This->{RootVertexSpecified}) { + return undef; + } + + # Use first vertex in sorted available vertices list to get root vertex. Vertex + # with largest degree could also be used as root vertex. However, for all + # practical purposes, any arbitrary vertex can be used as root vertex to + # start search for another disconnected component of the graph. + # + my(@VerticesToVisit); + + $RootVertexID = undef; @VerticesToVisit = (); + @VerticesToVisit = sort { $a <=> $b } keys %{$This->{VerticesToVisit}}; + if (@VerticesToVisit) { + $RootVertexID = $VerticesToVisit[0]; + } + return $RootVertexID; +} + +# Get current or new active vertex for DFS/BFS traversals... +# +sub _GetActiveVertex { + my($This) = @_; + my($ActiveVertexID); + + $ActiveVertexID = undef; + if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { + # For DFS, it's last vertex in LIFO queue... + $ActiveVertexID = $This->{ActiveVertices}[-1]; + } + elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { + # For BFS, it's first vertex in FIFO queue... + $ActiveVertexID = $This->{ActiveVertices}[0]; + } + return $ActiveVertexID; +} + +# Get available neigbor of specified vertex... +# +sub _GetNeighborVertex { + my($This, $VertexID) = @_; + + # Retrieve neighbors for vertex... + if (!exists $This->{VerticesNeighbors}{$VertexID}) { + @{$This->{VerticesNeighbors}{$VertexID}} = (); + + if (exists $This->{DepthLimit}) { + # Only collect neighbors to visit below specified depth limit... + if ($This->{VerticesDepth}{$VertexID} < $This->{DepthLimit}) { + push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); + } + else { + if (!exists $This->{RootVertexSpecified}) { + # Mark all other downstream neighbor vertices to be ignored from any further + # processing and avoid selection of a new root... + $This->_IgnoreDownstreamNeighbors($VertexID); + } + } + } + elsif (exists $This->{TargetVertex}) { + if ($VertexID != $This->{TargetVertex}) { + push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); + } + } + else { + push @{$This->{VerticesNeighbors}{$VertexID}}, $This->{Graph}->GetNeighbors($VertexID); + } + } + + if ($This->{TraversalMode} =~ /^Path$/i) { + # Get available neighbor for path search... + return $This->_GetNeighborVertexDuringPathTraversal($VertexID); + } + elsif ($This->{TraversalMode} =~ /^Vertex$/i) { + # Get unvisited neighbor for vertex search... + return $This->_GetNeighborVertexDuringVertexTraversal($VertexID); + } + elsif ($This->{TraversalMode} =~ /^VertexNeighborhood$/i) { + # Get available neighbor during vertex neighborhood search... + return $This->_GetNeighborVertexDuringVertexNeighborhoodTraversal($VertexID); + } + return undef; +} + +# Get unvisited neighbor of specified vertex during vertex traversal... +# +sub _GetNeighborVertexDuringVertexTraversal { + my($This, $VertexID) = @_; + my($NeighborVertexID, $UnvisitedNeighborVertexID); + + # Get unvisited neighbor... + $UnvisitedNeighborVertexID = undef; + NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { + if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + } + return $UnvisitedNeighborVertexID; +} + +# Get available neighbor of specified vertex during vertex neighborhood traversal... +# +sub _GetNeighborVertexDuringVertexNeighborhoodTraversal { + my($This, $VertexID) = @_; + my($NeighborVertexID, $UnvisitedNeighborVertexID); + + # Get available neighbor... + $UnvisitedNeighborVertexID = undef; + NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { + if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + # Look for any unvisited edge back to visited vertex... + if ($This->_IsVisitedEdge($VertexID, $NeighborVertexID) || $This->_IsVisitedEdge($NeighborVertexID, $VertexID)) { + next NEIGHBOR; + } + # Check its depth... + if (exists $This->{DepthLimit}) { + if (($This->{VerticesDepth}{$VertexID} + 1) > $This->{DepthLimit}) { + next NEIGHBOR; + } + } + # Its an edge that makes a cycle during BFS search... + if ($This->{AllowVertexCycles}) { + $This->{CycleClosureVertices}{$NeighborVertexID} = 1; + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + } + return $UnvisitedNeighborVertexID; +} + +# Get available neighbor of specified vertex during path traversal... +# +sub _GetNeighborVertexDuringPathTraversal { + my($This, $VertexID) = @_; + my($NeighborVertexID, $UnvisitedNeighborVertexID); + + # Get unvisited neighbor... + $UnvisitedNeighborVertexID = undef; + NEIGHBOR: for $NeighborVertexID (@{$This->{VerticesNeighbors}{$VertexID}}) { + if (!exists $This->{VisitedVertices}{$NeighborVertexID}) { + # An unvisited vertex... + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + # Look for any unvisited edge back to visited vertex... + if ($This->_IsVisitedEdge($VertexID, $NeighborVertexID) || $This->_IsVisitedEdge($NeighborVertexID, $VertexID)) { + next NEIGHBOR; + } + # Check its depth... + if (exists $This->{DepthLimit}) { + if (($This->{VerticesDepth}{$VertexID} + 1) >= $This->{DepthLimit}) { + next NEIGHBOR; + } + } + + # It's the edge final edge of a cycle in case $NeighborVertexID is already in the path; otherwise, it's + # part of the path from a different direction in a cycle or a left over vertex during Limit search. + # + if ($This->_IsCycleClosureEdge($VertexID, $NeighborVertexID)) { + if ($This->{AllowPathCycles}) { + $This->{CycleClosureVertices}{$NeighborVertexID} = 1; + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + } + else { + $UnvisitedNeighborVertexID = $NeighborVertexID; + last NEIGHBOR; + } + } + return $UnvisitedNeighborVertexID; +} + +# Process visited vertex... +# +sub _ProcessVisitedVertex { + my($This, $VertexID, $PredecessorVertexID) = @_; + + if (!exists $This->{VisitedVertices}{$VertexID}) { + # Add it to active vertices list... + push @{$This->{ActiveVertices}}, $VertexID; + + # Mark vertex as visited vertex and take it out from the list of vertices to visit... + $This->{VisitedVertices}{$VertexID} = 1; + delete $This->{VerticesToVisit}{$VertexID}; + } + + # Set up root vertex, predecessor vertex and distance from root... + if ($VertexID == $PredecessorVertexID) { + $This->{VerticesRoots}{$VertexID} = $VertexID; + + $This->{VerticesPredecessors}{$VertexID} = $VertexID; + if (!exists $This->{VerticesSuccessors}{$VertexID}) { + @{$This->{VerticesSuccessors}{$VertexID}} = (); + } + + $This->{VerticesDepth}{$VertexID} = 0; + + if ($This->{TraversalMode} =~ /^Path$/i) { + $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); + } + } + else { + $This->{VerticesRoots}{$VertexID} = $This->{VerticesRoots}{$PredecessorVertexID}; + + $This->{VerticesPredecessors}{$VertexID} = $PredecessorVertexID; + if (!exists $This->{VerticesSuccessors}{$PredecessorVertexID}) { + @{$This->{VerticesSuccessors}{$PredecessorVertexID}} = (); + } + push @{$This->{VerticesSuccessors}{$PredecessorVertexID}}, $VertexID; + + if (!exists $This->{VerticesDepth}{$VertexID}) { + $This->{VerticesDepth}{$VertexID} = $This->{VerticesDepth}{$PredecessorVertexID} + 1; + } + + if ($This->{TraversalMode} =~ /^Path$/i) { + $This->_ProcessVisitedPath($VertexID, $PredecessorVertexID); + $This->_ProcessVisitedEdge($PredecessorVertexID, $VertexID); + } + elsif ($This->{TraversalMode} =~ /^VertexNeighborhood$/i) { + $This->_ProcessVisitedEdge($PredecessorVertexID, $VertexID); + } + } + return $This; +} + +# Process visited path... +# +sub _ProcessVisitedPath { + my($This, $VertexID, $PredecessorVertexID) = @_; + + # Initialize VerticesPath... + if (!exists $This->{VerticesPaths}{$VertexID}) { + @{$This->{VerticesPaths}{$VertexID}} = (); + } + + if ($VertexID == $PredecessorVertexID) { + # Starting of a path from root... + push @{$This->{VerticesPaths}{$VertexID}}, $VertexID; + } + else { + # Setup path for a vertex using path information from predecessor vertex... + if (exists $This->{CycleClosureVertices}{$PredecessorVertexID}) { + # Start of a new path from predecessor vertex... + push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexID}-${VertexID}"; + } + else { + my($PredecessorVertexPath); + for $PredecessorVertexPath (@{$This->{VerticesPaths}{$PredecessorVertexID}}) { + push @{$This->{VerticesPaths}{$VertexID}}, "${PredecessorVertexPath}-${VertexID}"; + } + } + } + return $This; +} + +# Process visited edge... +# +sub _ProcessVisitedEdge { + my($This, $VertexID1, $VertexID2) = @_; + + if (!exists $This->{VisitedEdges}->{From}->{$VertexID1}) { + %{$This->{VisitedEdges}->{From}->{$VertexID1}} = (); + } + $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2} = $VertexID2; + + if (!exists $This->{VisitedEdges}->{To}->{$VertexID2}) { + %{$This->{VisitedEdges}->{To}->{$VertexID2}} = (); + } + $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1} = $VertexID1; + + return $This; +} + +# Finished processing active vertex... +# +sub _ProcessFinishedVertex { + my($This, $VertexID) = @_; + + if (!exists $This->{FinishedVertices}{$VertexID}) { + $This->{FinishedVertices}{$VertexID} = $VertexID; + # Add vertex to list of vertices found by traversal... + push @{$This->{Vertices}}, $VertexID; + } + + # Any active vertices left... + if (!@{$This->{ActiveVertices}}) { + return $This; + } + + # Take it off active vertices list... + if ($This->{TraversalType} =~ /^(DFS|DFSWithLimit)$/i) { + # For DFS, it's last vertex in LIFO queue... + pop @{$This->{ActiveVertices}}; + } + elsif ($This->{TraversalType} =~ /^(BFS|BFSWithLimit)$/i) { + # For BFS, it's first vertex in FIFO queue... + shift @{$This->{ActiveVertices}}; + } + return $This; +} + +# Mark all other downstream neighbor vertices to be ignored from any further +# processing... +# +sub _IgnoreDownstreamNeighbors { + my($This, $VertexID, $PredecessorVertexID) = @_; + + if (exists $This->{VerticesToVisit}{$VertexID}) { + # Mark vertex as visited vertex and take it out from the list of vertices to visit... + $This->{VisitedVertices}{$VertexID} = 1; + delete $This->{VerticesToVisit}{$VertexID}; + + if (defined($PredecessorVertexID) && $This->{TraversalMode} =~ /^(Path|VertexNeighborhood)$/i) { + $This->_ProcessVisitedEdge($VertexID, $PredecessorVertexID); + } + } + my($NeighborVertexID, @NeighborsVertexIDs); + + @NeighborsVertexIDs = (); + @NeighborsVertexIDs = $This->{Graph}->GetNeighbors($VertexID); + NEIGHBOR: for $NeighborVertexID (@NeighborsVertexIDs) { + if (!exists $This->{VerticesToVisit}{$NeighborVertexID}) { + # Avoid going back to predecessor vertex which has already been ignored... + next NEIGHBOR; + } + $This->_IgnoreDownstreamNeighbors($NeighborVertexID, $VertexID); + } + return $This; +} + +# Is it a visited edge? +# +sub _IsVisitedEdge { + my($This, $VertexID1, $VertexID2) = @_; + + if (exists $This->{VisitedEdges}->{From}->{$VertexID1}) { + if (exists $This->{VisitedEdges}->{From}->{$VertexID1}->{$VertexID2}) { + return 1; + } + } + elsif (exists $This->{VisitedEdges}->{To}->{$VertexID2}) { + if (exists $This->{VisitedEdges}->{To}->{$VertexID2}->{$VertexID1}) { + return 1; + } + } + return 0; +} + +# Is it a cycle closure edge? +# +# Notes: +# . Presence of VertexID2 in DFS path traversed for VertexID1 make it a cycle +# closure edge... +# +sub _IsCycleClosureEdge { + my($This, $VertexID1, $VertexID2) = @_; + + if (!exists $This->{VerticesPaths}{$VertexID1}) { + return 0; + } + my($Path); + for $Path (@{$This->{VerticesPaths}{$VertexID1}}) { + if (($Path =~ /-$VertexID2-/ || $Path =~ /^$VertexID2-/ || $Path =~ /-$VertexID2$/)) { + return 1; + } + } + return 0; +} + +# Search paths starting from a specified vertex with no sharing of edges in paths traversed. +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformPathsSearch { + my($This, $StartVertexID, $AllowCycles) = @_; + + # Make sure start vertex is defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Start vertex must be specified..."; + return undef; + } + + # Make sure start vertex is valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + + if (!defined $AllowCycles) { + $AllowCycles = 1; + } + + # Perform paths search... + return $This->_PerformPathsSearch("AllLengths", $StartVertexID, $AllowCycles); +} + +# Search paths starting from a specified vertex with length upto a specified length +# with no sharing of edges in paths traversed... +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformPathsSearchWithLengthUpto { + my($This, $StartVertexID, $Length, $AllowCycles) = @_; + + return $This->_PerformPathsSearchWithLength("LengthUpto", $StartVertexID, $Length, $AllowCycles); +} + +# Search paths starting from a specified vertex with specified length +# with no sharing of edges in paths traversed... +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformPathsSearchWithLength { + my($This, $StartVertexID, $Length, $AllowCycles) = @_; + + return $This->_PerformPathsSearchWithLength("Length", $StartVertexID, $Length, $AllowCycles); +} + + +# Search paths starting from a specified vertex with length upto a specified length +# with no sharing of edges in paths traversed... +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub _PerformPathsSearchWithLength { + my($This, $Mode, $StartVertexID, $Length, $AllowCycles) = @_; + + # Make sure both start vertex and length are defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Start vertex must be specified..."; + return undef; + } + if (!defined $Length) { + carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Length must be specified..."; + return undef; + } + + if (!defined $AllowCycles) { + $AllowCycles = 1; + } + + # Make sure both start vertex and length are valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + + if ($Length < 1) { + carp "Warning: ${ClassName}->_PerformPathsSearchWithLength: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; + return undef; + } + + # Perform paths search... + return $This->_PerformPathsSearch($Mode, $StartVertexID, $AllowCycles, $Length); +} + +# Search all paths starting from a specified vertex with sharing of edges in paths traversed... +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformAllPathsSearch { + my($This, $StartVertexID, $AllowCycles) = @_; + + # Make sure start vertex is defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Start vertex must be specified..."; + return undef; + } + + # Make sure start vertex is valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformAllPathsSearch: No paths search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + + if (!defined $AllowCycles) { + $AllowCycles = 1; + } + + # Perform paths search... + return $This->_PerformAllPathsSearch("AllLengths", $StartVertexID, $AllowCycles); +} + +# Search all paths starting from a specified vertex with length upto a specified length with sharing of +# edges in paths traversed. +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformAllPathsSearchWithLengthUpto { + my($This, $StartVertexID, $Length, $AllowCycles) = @_; + + return $This->_PerformAllPathsSearchWithLength("LengthUpto", $StartVertexID, $Length, $AllowCycles); +} + +# Search all paths starting from a specified vertex with specified length with sharing of +# edges in paths traversed. +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub PerformAllPathsSearchWithLength { + my($This, $StartVertexID, $Length, $AllowCycles) = @_; + + return $This->_PerformAllPathsSearchWithLength("Length", $StartVertexID, $Length, $AllowCycles); +} + +# Search all paths starting from a specified vertex with length upto a specified length with sharing of +# edges in paths traversed. +# +# By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +# completing the cycle. +# +sub _PerformAllPathsSearchWithLength { + my($This, $Mode, $StartVertexID, $Length, $AllowCycles) = @_; + + # Make sure both start vertex and length are defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Start vertex must be specified..."; + return undef; + } + if (!defined $Length) { + carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Length must be specified..."; + return undef; + } + + if (!defined $AllowCycles) { + $AllowCycles = 1; + } + + # Make sure both start vertex and length are valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + + if ($Length < 1) { + carp "Warning: ${ClassName}->_PerformAllPathsSearchWithLength: No paths search performed: Specified length, $Length, must be a positive integer with value greater than 1..."; + return undef; + } + + # Perform paths search... + return $This->_PerformAllPathsSearch($Mode, $StartVertexID, $AllowCycles, $Length); +} + +# Search paths between two vertices... +# +sub PerformPathsSearchBetween { + my($This, $StartVertexID, $EndVertexID) = @_; + + # Make sure start and end vertices are defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: Start vertex must be specified..."; + return undef; + } + if (!defined $EndVertexID) { + carp "Warning: ${ClassName}->PerformPathsSearchBetweeb: No paths search performed: EndVertex vertex must be specified..."; + return undef; + } + # Make sure start and end vertices are valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + if (!$This->{Graph}->HasVertex($EndVertexID)) { + carp "Warning: ${ClassName}->PerformPathsSearchBetween: No paths search performed: Vertex $EndVertexID doesn't exist..."; + return undef; + } + + # Perform paths search... + return $This->_PerformPathsSearchBetween($StartVertexID, $EndVertexID); +} + +# Search paths starting from root vertex with no sharing of edges... +# +# Notes: +# . Possible paths searche modes are: DFSPathsWithLimit, DFSPaths. And each +# of these modes supports any combination of two options: CommonEdges, Cycles. +# Default for CommonEdges - No; Cycles - No. +# +sub _PerformPathsSearch { + my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; + + # Perform DFS path search... + + $This->{TraversalMode} = 'Path'; + + if ($Mode =~ /^(LengthUpto|Length)$/i) { + my($DepthLimit); + + $DepthLimit = $Length - 1; + $This->{TraversalType} = 'DFSWithLimit'; + $This->{DepthLimit} = $DepthLimit; + } + else { + $This->{TraversalType} = 'DFS'; + } + if (defined $RootVertexID) { + $This->{RootVertex} = $RootVertexID; + } + + $This->{AllowPathCycles} = $AllowCycles; + + # Perform search... + $This->_TraverseGraph(); + + # Make sure traversal did get the root vertex... + if (!exists $This->{VerticesDepth}{$RootVertexID}) { + return $This; + } + if ($Mode =~ /^Length$/i) { + push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearchWithLength($Length); + } + else { + push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); + } + + return $This; +} + +# Search all paths starting from root vertex with sharing of edges... +# +sub _PerformAllPathsSearch { + my($This, $Mode, $RootVertexID, $AllowCycles, $Length) = @_; + + # Perform DFS path search... + + $This->{TraversalMode} = 'AllPaths'; + + if ($Mode =~ /^(LengthUpto|Length)$/i) { + my($DepthLimit); + + $DepthLimit = $Length - 1; + $This->{TraversalType} = 'DFSWithLimit'; + $This->{DepthLimit} = $DepthLimit; + } + else { + $This->{TraversalType} = 'DFS'; + } + $This->{RootVertex} = $RootVertexID; + $This->{AllowPathCycles} = $AllowCycles; + + # Traverse all paths search using DFS search... + $This->_TraverseAllPathsInGraph($Mode, $Length); + + return $This; +} + +# Travese all paths in graph starting from a specified root vertex... +# +sub _TraverseAllPathsInGraph { + my($This, $Mode, $Length) = @_; + + if ($This->{TraversalMode} !~ /^AllPaths$/i) { + return $This; + } + my($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); + + $CurrentVertexID = $This->{RootVertex}; + $PredecessorVertexID = $CurrentVertexID; + $CurrentDepth = 0; + $CurrentPath = "$CurrentVertexID"; + + $This->_TraverseAllPaths($CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath); + + if ($Mode =~ /^Length$/i) { + push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearchWithLength($Length); + } + else { + push @{$This->{Paths}}, $This->_CollectPathsTraversedDuringPathsSearch(); + } + + return $This; +} + +# Traverse and collect all paths recuresively.. +# +sub _TraverseAllPaths { + my($This, $CurrentVertexID, $PredecessorVertexID, $CurrentDepth, $CurrentPath) = @_; + + # Save path traversed for current vertex.. + if (!exists $This->{VerticesPaths}{$CurrentVertexID}) { + @{$This->{VerticesPaths}{$CurrentVertexID}} = (); + $This->{VerticesDepth}{$CurrentVertexID} = 0; + } + push @{$This->{VerticesPaths}{$CurrentVertexID}}, $CurrentPath; + $This->{VerticesDepth}{$CurrentVertexID} = $CurrentDepth; + + $CurrentDepth++; + if (exists $This->{DepthLimit}) { + if ($CurrentDepth > $This->{DepthLimit}) { + # Nothing more to do... + return $This; + } + } + my($NeighborVertexID, $NewPath); + + NEIGHBOR: for $NeighborVertexID ($This->{Graph}->GetNeighbors($CurrentVertexID)) { + if ($NeighborVertexID == $PredecessorVertexID) { + next NEIGHBOR; + } + if ($This->_IsVertexInTraversedPath($NeighborVertexID, $CurrentPath)) { + # It's a cycle... + if ($This->{AllowPathCycles}) { + $NewPath = "${CurrentPath}-${NeighborVertexID}"; + if (!exists $This->{VerticesPaths}{$NeighborVertexID}) { + @{$This->{VerticesPaths}{$NeighborVertexID}} = (); + } + push @{$This->{VerticesPaths}{$NeighborVertexID}}, $NewPath; + } + next NEIGHBOR; + } + $NewPath = "${CurrentPath}-${NeighborVertexID}"; + $This->_TraverseAllPaths($NeighborVertexID, $CurrentVertexID, $CurrentDepth, $NewPath); + } + return $This; +} + +# Is vertex already in traversed path? +# +sub _IsVertexInTraversedPath { + my($This, $VertexID, $Path) = @_; + + return ($Path =~ /-$VertexID-/ || $Path =~ /^$VertexID-/ || $Path =~ /-$VertexID$/) ? 1 : 0; +} + +# Collect all paths traversed during Path TraversalMode and sort 'em in +# ascending order of lengths +# +sub _CollectPathsTraversedDuringPathsSearch { + my($This) = @_; + my($VertexID, @Paths, @SortedPaths); + + @Paths = (); @SortedPaths = (); + + # Create path objects from path vertex strings... + for $VertexID (keys %{$This->{VerticesPaths}}) { + push @Paths, map { new Graph::Path(split /-/, $_) } @{$This->{VerticesPaths}{$VertexID}}; + } + + # Sort paths in ascending order of lengths... + push @SortedPaths, sort { $a->GetLength() <=> $b->GetLength() } @Paths; + + return @SortedPaths; +} + +# Collect paths traversed during Path TraversalMode with specific length... +# +sub _CollectPathsTraversedDuringPathsSearchWithLength { + my($This, $Length) = @_; + my($VertexID, $Depth, $PathString, @VertexIDs, @Paths); + + @Paths = (); + $Depth = $Length - 1; + + # Create path objects from path vertex strings... + VERTEXID: for $VertexID (keys %{$This->{VerticesPaths}}) { + if ($This->{VerticesDepth}{$VertexID} != $Depth) { + next VERTEXID; + } + # For vertices involved in cycles, the path might also contain some shorter paths. So check + # the lengths before its collection... + PATHSTRING: for $PathString (@{$This->{VerticesPaths}{$VertexID}}) { + @VertexIDs = split /-/, $PathString; + if ($Length != @VertexIDs) { + next PATHSTRING; + } + push @Paths, new Graph::Path(@VertexIDs); + } + } + return @Paths; +} + +# Collect paths traversed during Vertex TraversalMode... +# +sub _CollectPathsTraversedDuringVertexSearch { + my($This, $RootVertexID) = @_; + my($Depth, @Paths, @VerticesAtDepth); + @Paths = (); + + # Get vertices at specific depths... + @VerticesAtDepth = (); + @VerticesAtDepth = $This->_CollectVerticesAtSpecificDepths(); + if (!@VerticesAtDepth) { + return @Paths; + } + + # Make sure search found only one root vertex and it corresponds to + # what was specified... + $Depth = 0; + if ((@{$VerticesAtDepth[$Depth]} > 1) || ($VerticesAtDepth[$Depth][0] != $RootVertexID)) { + carp "Warning: ${ClassName}->_PerformPathsSearch: No paths found: Root vertex, $VerticesAtDepth[$Depth][0], identified by paths traversal doen't match specified root vertex $RootVertexID..."; + return @Paths; + } + + # Setup root vertex at depth 0. And set its path... + my($Path, $VertexID, $SuccessorVertexID, @VertexIDs, %PathAtVertex); + %PathAtVertex = (); + $PathAtVertex{$RootVertexID} = new Graph::Path($RootVertexID); + + for $Depth (0 .. $#VerticesAtDepth) { + # Go over all vertices at current depth... + VERTEX: for $VertexID (@{$VerticesAtDepth[$Depth]}) { + if (!exists $This->{VerticesSuccessors}{$VertexID}) { + next VERTEX; + } + # Get vertices for current path... + @VertexIDs = (); + push @VertexIDs, $PathAtVertex{$VertexID}->GetVertices; + + # Expand path to successor vertex found during traversal... + for $SuccessorVertexID (@{$This->{VerticesSuccessors}{$VertexID}}) { + $Path = new Graph::Path(@VertexIDs); + $Path->AddVertex($SuccessorVertexID); + $PathAtVertex{$SuccessorVertexID} = $Path; + } + } + } + # Sort paths in ascending order of lengths... + push @Paths, sort { $a->GetLength() <=> $b->GetLength() } values %PathAtVertex; + + return @Paths; +} + +# Collect vertices at specific depths. Depth values start from 0... +# +sub _CollectVerticesAtSpecificDepths { + my($This) = @_; + my($VertexID, $Depth, @VerticesAtDepth); + + @VerticesAtDepth = (); + while (($VertexID, $Depth) = each %{$This->{VerticesDepth}}) { + push @{$VerticesAtDepth[$Depth]}, $VertexID; + } + return @VerticesAtDepth; +} + +# Collect vertices, along with their successors, at specific depths and return a list containing references to +# lists with first value corresponding to vertex ID and second value a reference to a list containing +# its successors. +# +# Depth values start from 0... +# +sub _CollectVerticesWithSuccessorsAtSpecificDepths { + my($This) = @_; + my($VertexID, $Depth, @VerticesWithSuccessorsAtDepth); + + @VerticesWithSuccessorsAtDepth = (); + while (($VertexID, $Depth) = each %{$This->{VerticesDepth}}) { + my(@VertexWithSuccessors, @VertexSuccessors); + + @VertexWithSuccessors = (); @VertexSuccessors = (); + if (exists $This->{VerticesSuccessors}{$VertexID}) { + push @VertexSuccessors, @{$This->{VerticesSuccessors}{$VertexID}}; + } + push @VertexWithSuccessors, ($VertexID, \@VertexSuccessors); + # Multiple entries for a vertex and its successors could be present at a specific depth... + push @{$VerticesWithSuccessorsAtDepth[$Depth]}, \@VertexWithSuccessors; + } + return @VerticesWithSuccessorsAtDepth; +} + +# Search paths between two vertices... +# +sub _PerformPathsSearchBetween { + my($This, $RootVertexID, $TargetVertexID) = @_; + my($DepthLimit); + + # Perform a targeted DFS search... + $DepthLimit = undef; + $This->_PerformVertexSearch("DFS", $RootVertexID, $DepthLimit, $TargetVertexID); + + my($Path); + $Path = $This->_CollectPathBetween($RootVertexID, $TargetVertexID); + + if (defined $Path) { + push @{$This->{Paths}}, $Path; + } + return $This; +} + +# Collect path between root and target vertex after the search... +# +sub _CollectPathBetween { + my($This, $RootVertexID, $TargetVertexID) = @_; + + # Does a path from root to target vertex exist? + if (!(exists($This->{VerticesRoots}{$TargetVertexID}) && ($This->{VerticesRoots}{$TargetVertexID} == $RootVertexID))) { + return undef; + } + + # Add target vertex ID path vertices... + my($VertexID, $Path, @VertexIDs); + @VertexIDs = (); + $VertexID = $TargetVertexID; + push @VertexIDs, $VertexID; + + # Backtrack to root vertex ID... + while ($This->{VerticesPredecessors}{$VertexID} != $VertexID) { + $VertexID = $This->{VerticesPredecessors}{$VertexID}; + push @VertexIDs, $VertexID; + } + + # Create path from target to root and reverse it... + $Path = new Graph::Path(@VertexIDs); + $Path->Reverse(); + + return $Path; +} + +# Search vertices around specified root vertex with in specific neighborhood radius... +# +sub PerformNeighborhoodVerticesSearchWithRadiusUpto { + my($This, $StartVertexID, $Radius) = @_; + + # Make sure both start vertex and radius are defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Start vertex must be specified..."; + return undef; + } + if (!defined $Radius) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Radius must be specified..."; + return undef; + } + + # Make sure both start vertex and length are valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + if ($Radius < 0) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithRadiusUpto: No vertices search performed: Specified radius, $Radius, must be a positive integer..."; + return undef; + } + + # Perform vertices search... + return $This->_PerformNeighborhoodVerticesSearch("RadiusUpto", $StartVertexID, $Radius); +} + +# Search vertices around specified root vertex... +# +sub PerformNeighborhoodVerticesSearch { + my($This, $StartVertexID) = @_; + + # Make sure start vertex is defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Start vertex must be specified..."; + return undef; + } + + # Make sure start vertex is valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearch: No vertices search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + # Perform vertices search... + return $This->_PerformNeighborhoodVerticesSearch("AllRadii", $StartVertexID); +} + +# Search vertices around specified root vertex with in specific neighborhood radius along with +# identification of successors of each vertex found during the search... +# +sub PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto { + my($This, $StartVertexID, $Radius) = @_; + + # Make sure both start vertex and radius are defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Start vertex must be specified..."; + return undef; + } + if (!defined $Radius) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Radius must be specified..."; + return undef; + } + + # Make sure both start vertex and length are valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + if ($Radius < 0) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto: No vertices search performed: Specified radius, $Radius, must be a positive integer..."; + return undef; + } + + # Perform vertices search... + return $This->_PerformNeighborhoodVerticesSearch("WithSuccessorsAndRadiusUpto", $StartVertexID, $Radius); +} + +# Search vertices around specified root vertex along with identification of +# successors of each vertex found during the search... +# +sub PerformNeighborhoodVerticesSearchWithSuccessors { + my($This, $StartVertexID) = @_; + + # Make sure start vertex is defined... + if (!defined $StartVertexID) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessors: No vertices search performed: Start vertex must be specified..."; + return undef; + } + + # Make sure start vertex is valid... + if (!$This->{Graph}->HasVertex($StartVertexID)) { + carp "Warning: ${ClassName}->PerformNeighborhoodVerticesSearchWithSuccessors: No vertices search performed: Vertex $StartVertexID doesn't exist..."; + return undef; + } + # Perform vertices search... + return $This->_PerformNeighborhoodVerticesSearch("WithSuccessorsAndAllRadii", $StartVertexID); +} + +# Search vertices at successive neighborhood radii levels... +# +sub _PerformNeighborhoodVerticesSearch { + my($This, $Mode, $RootVertexID, $Radius) = @_; + my($DepthLimit, $AllowCycles); + + $DepthLimit = defined $Radius ? $Radius : undef; + $AllowCycles = undef; + + # Perform BFS search... + if ($Mode =~ /^RadiusUpto$/i) { + $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit); + } + elsif ($Mode =~ /^(AllRadii)$/i) { + $This->_PerformVertexNeighborhoodSearch("BFS", $RootVertexID); + } + elsif ($Mode =~ /^WithSuccessorsAndRadiusUpto$/i) { + $AllowCycles = 1; + $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit, $AllowCycles); + } + elsif ($Mode =~ /^WithSuccessorsAndAllRadii$/i) { + $AllowCycles = 1; + $This->_PerformVertexNeighborhoodSearch("BFSWithLimit", $RootVertexID, $DepthLimit, $AllowCycles); + } + + # Make sure traversal did get the root vertex... + if (!exists $This->{VerticesDepth}{$RootVertexID}) { + return $This; + } + + if ($Mode =~ /^(RadiusUpto|AllRadii)$/i) { + push @{$This->{VerticesNeighborhoods}}, $This->_CollectVerticesAtSpecificDepths(); + } + elsif ($Mode =~ /^(WithSuccessorsAndRadiusUpto|WithSuccessorsAndAllRadii)$/i) { + push @{$This->{VerticesNeighborhoodsWithSuccessors}}, $This->_CollectVerticesWithSuccessorsAtSpecificDepths(); + } + + return $This; +} + +# Perform appropriate vertex search... +# +sub _PerformVertexNeighborhoodSearch { + my($This, $SearchType, $RootVertexID, $DepthLimit, $AllowCycles) = @_; + + # Setup search... + $This->{TraversalMode} = 'VertexNeighborhood'; + $This->{TraversalType} = $SearchType; + + if (defined $RootVertexID) { + $This->{RootVertex} = $RootVertexID; + } + if (defined $DepthLimit) { + $This->{DepthLimit} = $DepthLimit; + } + if (defined $AllowCycles) { + $This->{AllowVertexCycles} = $AllowCycles; + } + + # Perform search... + return $This->_TraverseGraph(); +} + +# Get orderded list of vertices after DFS/BFS search... +# +sub GetVertices { + my($This) = @_; + + return wantarray ? @{$This->{Vertices}} : scalar @{$This->{Vertices}}; +} + +# Get a hash list containing vertex and root vertex as key/value pair for all vertices +# ordered using DFS/BFS search available via GetVertices method... +# +sub GetVerticesRoots { + my($This) = @_; + + return %{$This->{VerticesRoots}}; +} + +# Get a list containing lists of vertices in connected components of graph after DFS/BFS +# search... +# +# Note: +# . List is sorted in descending order of number of vertices in each connected component. +# +sub GetConnectedComponentsVertices { + my($This) = @_; + my($VertexID, $VertexRoot, @ConnectedVertices, %VerticesAtRoot); + + @ConnectedVertices = (); + %VerticesAtRoot = (); + for $VertexID (@{$This->{Vertices}}) { + $VertexRoot = $This->{VerticesRoots}{$VertexID}; + if (!exists $VerticesAtRoot{$VertexRoot}) { + @{$VerticesAtRoot{$VertexRoot}} = (); + } + push @{$VerticesAtRoot{$VertexRoot}}, $VertexID; + } + push @ConnectedVertices, sort { @{$b} <=> @{$a} } values %VerticesAtRoot; + + return wantarray ? @ConnectedVertices : scalar @ConnectedVertices; +} + +# Get predecessor vertices... +# +sub GetVerticesPredecessors { + my($This) = @_; + + return %{$This->{VerticesPredecessors}}; +} + +# Get a hash list containing vertex and depth from root vertex as key/value pair for all vertices +# ordered using DFS/BFS search available via GetVertices method... +# +sub GetVerticesDepth { + my($This) = @_; + + return %{$This->{VerticesDepth}}; +} + +# Get paths found during paths search... +# +sub GetPaths { + my($This) = @_; + + return wantarray ? @{$This->{Paths}} : scalar @{$This->{Paths}}; +} + +# Get vertices collected at various neighborhood radii... +# +sub GetVerticesNeighborhoods { + my($This) = @_; + + return wantarray ? @{$This->{VerticesNeighborhoods}} : scalar @{$This->{VerticesNeighborhoods}}; +} + +# Get vertices, along with their successor vertices, collected at various neighborhood radii as +# a list containing references to lists with first value corresponding to vertex ID and second value +# a reference to a list containing its successors. +# +sub GetVerticesNeighborhoodsWithSuccessors { + my($This) = @_; + + return wantarray ? @{$This->{VerticesNeighborhoodsWithSuccessors}} : scalar @{$This->{VerticesNeighborhoodsWithSuccessors}}; +} + +# Return a string containg data for PathsTraversal object... +sub StringifyPathsTraversal { + my($This) = @_; + my($PathsTraversalString); + + $PathsTraversalString = "PathsTraversalMode: " . $This->{TraversalMode}; + $PathsTraversalString .= "; PathsTraversalType: " . $This->{TraversalType}; + + # Vertices ordered by traversal... + $PathsTraversalString .= "; Vertices: " . join(' ', @{$This->{Vertices}}); + + # Stringify depths of vertices... + $PathsTraversalString .= "; " . $This->StringifyVerticesDepth(); + + # Stringify roots of vertices... + $PathsTraversalString .= "; " . $This->StringifyVerticesRoots(); + + # Stringify predecessor of vertices... + $PathsTraversalString .= "; " . $This->StringifyVerticesPredecessors(); + + # Stringify successor vertices... + $PathsTraversalString .= "; " . $This->StringifyVerticesSuccessors(); + + # Stringify paths... + $PathsTraversalString .= "; " . $This->StringifyPaths(); + + # Stringify vertices neighborhoods... + $PathsTraversalString .= "; " . $This->StringifyVerticesNeighborhoods(); + + # Stringify vertices neighborhoods with successors... + $PathsTraversalString .= "; " . $This->StringifyVerticesNeighborhoodsWithSuccessors(); + + return $PathsTraversalString; +} + +# Stringify vertices depth... +# +sub StringifyVerticesDepth { + my($This) = @_; + my($VertexID, $VertexDepth, $DepthString); + + if (!@{$This->{Vertices}}) { + $DepthString = "<Vertex-Depth>: None"; + return $DepthString; + } + + $DepthString = "<Vertex-Depth>: "; + for $VertexID (@{$This->{Vertices}}) { + $VertexDepth = $This->{VerticesDepth}{$VertexID}; + $DepthString .= " <$VertexID-$VertexDepth>"; + } + return $DepthString; +} + +# Stringify roots of vertices... +# +sub StringifyVerticesRoots { + my($This) = @_; + my($VertexID, $RootVertexID, $RootsString); + + if (!@{$This->{Vertices}}) { + $RootsString = "<Vertex-RootVertex>: None"; + return $RootsString; + } + + $RootsString = "<Vertex-RootVertex>: "; + for $VertexID (@{$This->{Vertices}}) { + $RootVertexID = $This->{VerticesRoots}{$VertexID}; + $RootsString .= " <$VertexID-$RootVertexID>"; + } + return $RootsString; +} + +# Stringify predecessor of vertices... +# +sub StringifyVerticesPredecessors { + my($This) = @_; + my($VertexID, $PredecessorVertexID, $PredecessorString); + + if (!@{$This->{Vertices}}) { + $PredecessorString = "<Vertex-PredecessorVertex>: None"; + return $PredecessorString; + } + + $PredecessorString = "<Vertex-PredecessorVertex>: "; + for $VertexID (@{$This->{Vertices}}) { + $PredecessorVertexID = $This->{VerticesPredecessors}{$VertexID}; + $PredecessorString .= " <$VertexID-$PredecessorVertexID>"; + } + return $PredecessorString; +} + +# Stringify successor vertices... +# +sub StringifyVerticesSuccessors { + my($This) = @_; + my($VertexID, $SuccessorString, $VerticesSuccessorsString); + + if (!@{$This->{Vertices}}) { + $SuccessorString = "<Vertex-VerticesSuccessorsList>: None"; + return $SuccessorString; + } + + $SuccessorString = "<Vertex-VerticesSuccessorsList>: "; + for $VertexID (@{$This->{Vertices}}) { + if (exists($This->{VerticesSuccessors}{$VertexID}) && @{$This->{VerticesSuccessors}{$VertexID}}) { + $VerticesSuccessorsString = join(',', @{$This->{VerticesSuccessors}{$VertexID}}); + } + else { + $VerticesSuccessorsString = "None"; + } + $SuccessorString .= " <$VertexID-$VerticesSuccessorsString>"; + } + return $SuccessorString; +} + +# Strinigify paths... +# +sub StringifyPaths { + my($This) = @_; + my($PathsString, $Path); + + if (!@{$This->{Paths}}) { + $PathsString = "Paths: None"; + return $PathsString; + } + + my($FirstPath); + $PathsString = "Paths: "; + $FirstPath = 1; + for $Path (@{$This->{Paths}}) { + if ($FirstPath) { + $FirstPath = 0; + } + else { + $PathsString .= " "; + } + $PathsString .= "<" . join('-', $Path->GetVertices()) . ">"; + } + return $PathsString; +} + +# Strinigify vertices neighborhoods... +# +sub StringifyVerticesNeighborhoods { + my($This) = @_; + my($NeighborhoodsString, $NeighborhoodVerticesString, $Radius); + + if (!@{$This->{VerticesNeighborhoods}}) { + $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVerticesList>: None"; + return $NeighborhoodsString; + } + $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVerticesList>:"; + for $Radius (0 .. $#{$This->{VerticesNeighborhoods}}) { + $NeighborhoodVerticesString = join(',', @{$This->{VerticesNeighborhoods}[$Radius]}); + $NeighborhoodsString .= " <$Radius-$NeighborhoodVerticesString>"; + } + + return $NeighborhoodsString; +} + +# Strinigify vertices neighborhoods... +# +sub StringifyVerticesNeighborhoodsWithSuccessors { + my($This) = @_; + my($NeighborhoodsString, $NeighborhoodVertexSuccessorsString, $Radius, $NeighborhoodVertericesWithSuccessorsRef, $NeighborhoodVertexWithSuccessorsRef, $VertexID, $NeighborhoodVertexSuccessorsRef); + + if (!@{$This->{VerticesNeighborhoodsWithSuccessors}}) { + $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVertex-NeighborhoodVerticeSuccessorsList>: None"; + return $NeighborhoodsString; + } + $NeighborhoodsString = "<NeighborhoodRadius-NeighborhoodVertex-NeighborhoodVerticeSuccessorsList>: None"; + + $Radius = 0; + for $NeighborhoodVertericesWithSuccessorsRef (@{$This->{VerticesNeighborhoodsWithSuccessors}}) { + for $NeighborhoodVertexWithSuccessorsRef (@{$NeighborhoodVertericesWithSuccessorsRef}) { + ($VertexID, $NeighborhoodVertexSuccessorsRef) = @{$NeighborhoodVertexWithSuccessorsRef}; + $NeighborhoodVertexSuccessorsString = 'None'; + if (@{$NeighborhoodVertexSuccessorsRef}) { + $NeighborhoodVertexSuccessorsString = join(',', @{$NeighborhoodVertexSuccessorsRef}); + } + $NeighborhoodsString .= " <$Radius-$VertexID-$NeighborhoodVertexSuccessorsString>"; + } + $Radius++; + } + return $NeighborhoodsString; +} + +# Return a reference to new paths traversal object... +sub Copy { + my($This) = @_; + my($NewPathsTraversal); + + $NewPathsTraversal = Storable::dclone($This); + + return $NewPathsTraversal; +} + +1; + +__END__ + +=head1 NAME + +PathsTraversal + +=head1 SYNOPSIS + +use Graph::PathsTraversal; + +use Graph::PathsTraversal qw(:all); + +=head1 DESCRIPTION + +B<PathsTraversal> class provides the following methods: + +new, Copy, GetConnectedComponentsVertices, GetPaths, GetVertices, +GetVerticesDepth, GetVerticesNeighborhoods, +GetVerticesNeighborhoodsWithSuccessors, GetVerticesPredecessors, GetVerticesRoots, +PerformAllPathsSearch, PerformAllPathsSearchWithLength, +PerformAllPathsSearchWithLengthUpto, PerformBreadthFirstSearch, +PerformBreadthFirstSearchWithLimit, PerformDepthFirstSearch, +PerformDepthFirstSearchWithLimit, PerformNeighborhoodVerticesSearch, +PerformNeighborhoodVerticesSearchWithRadiusUpto, +PerformNeighborhoodVerticesSearchWithSuccessors, +PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto, PerformPathsSearch, +PerformPathsSearchBetween, PerformPathsSearchWithLength, +PerformPathsSearchWithLengthUpto, StringifyPaths, StringifyPathsTraversal, +StringifyVerticesDepth, StringifyVerticesNeighborhoods, +StringifyVerticesNeighborhoodsWithSuccessors, StringifyVerticesPredecessors, +StringifyVerticesRoots, StringifyVerticesSuccessors + +=head2 METHODS + +=over 4 + +=item B<new> + + $PathsTraversal = new Graph::PathsTraversal($Graph); + +Using specified I<Graph>, B<new> method creates a new B<PathsTraversal> object and returns +newly created B<PathsTraversal> object. + +=item B<Copy> + + $PathsTraversal = $PathsTraversal->Copy(); + +Copies I<PathsTraversal> and its associated data using B<Storable::dclone> and returns a new +B<PathsTraversal> object. + +=item B<GetConnectedComponentsVertices> + + @Components = $PathsTraversal->GetConnectedComponentsVertices(); + $NumOfComponents = $PathsTraversal->GetConnectedComponentsVertices(); + +Returns an array of B<Components> containing references to arrays of vertex IDs corresponding +to connected components of graph after a search. In scalar context, the number of connected +components is returned. + +Connected B<Components> is sorted in descending order of number of vertices in each +connected component. + +=item B<GetPaths> + + @Paths = $PathsTraversal->GetPaths(); + $NumOfPaths = $PathsTraversal->GetPaths(); + +Returns an array of B<Paths> containing references to arrays of vertex IDs corresponding to +to paths traversed in a graph after a search. In scalar context, number of paths is returned. + +B<Paths> array is sorted in ascending order of path lengths. + +=item B<GetVertices> + + @Vertices = $PathsTraversal->GetVertices(); + $NumOfVertices = $PathsTraversal->GetVertices(); + +Returns an array containing an ordered list of vertex IDs traversed during a search. In +scalar context, the number of vertices is returned. + +=item B<GetVerticesDepth> + + %VerticesDepth = $PathsTraversal->GetVerticesDepth(); + +Returns a hash I<VerticesDepth> containing vertex ID and depth from root vertex as a key and +value pair for all vertices traversed during a search. + +=item B<GetVerticesNeighborhoods> + + @VerticesNeighborhoods = + $PathsTraversal->GetVerticesNeighborhoods(); + $NumOfVerticesNeighborhoods = + $PathsTraversal->GetVerticesNeighborhoods(); + +Returns an array I<VerticesNeighborhoods> containing references to arrays corresponding +to vertices collected at various neighborhood radii around a specified vertex during a vertex +neighborhood search. In scalar context, the number of neighborhoods is returned. + +=item B<GetVerticesNeighborhoodsWithSuccessors> + + @VerticesNeighborhoodsWithSucceessors = + $PathsTraversal->GetVerticesNeighborhoodsWithSuccessors(); + $NumOfVerticesNeighborhoodsWithSucceessors = + $PathsTraversal->GetVerticesNeighborhoodsWithSuccessors(); + +Returns an array I<VerticesNeighborhoodsWithSucceessors> containing references to arrays +with first value corresponding to vertex IDs corresponding to a vertex at a specific neighborhood +radius level and second value a reference to an arraty containing its successors. + +=item B<GetVerticesPredecessors> + + %VerticesPredecessors = $PathsTraversal->GetVerticesPredecessors(); + +Returns a hash I<VerticesPredecessors> containing vertex ID and predecessor vertex ID as key +and value pair for all vertices traversed during a search. + +=item B<GetVerticesRoots> + + %VerticesRoots = $PathsTraversal->GetVerticesRoots(); + +Returns a hash I<VerticesPredecessors> containing vertex ID and root vertex ID as a key +and value pair for all vertices traversed during a search. + +=item B<PerformAllPathsSearch> + + $PathsTraversal->PerformAllPathsSearch($StartVertexID, [$AllowCycles]); + +Searches all paths starting from a I<StartVertexID> with sharing of edges in paths traversed and +returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<PerformAllPathsSearchWithLength> + + $PathsTraversal->PerformAllPathsSearchWithLength($StartVertexID, + $Length, [$AllowCycles]); + +Searches all paths starting from I<StartVertexID> of specific I<Length> with sharing of +edges in paths traversed and returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<PerformAllPathsSearchWithLengthUpto> + + $PathsTraversal->PerformAllPathsSearchWithLengthUpto($StartVertexID, + $Length, [$AllowCycles]); + +Searches all paths starting from I<StartVertexID> of length upto a I<Length> with sharing of +edges in paths traversed and returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<PerformBreadthFirstSearch> + + $PathsTraversal->PerformBreadthFirstSearch(); + +Performs Breadth First Search (BFS) and returns I<PathsTraversal>. + +=item B<PerformBreadthFirstSearchWithLimit> + + $PathsTraversal->PerformBreadthFirstSearchWithLimit($DepthLimit, + [$RootVertexID]); + +Performs BFS with depth up to I<DepthLimit> starting at I<RootVertexID> and returns +I<PathsTraversal>. By default, root vertex ID corresponds to an arbitrary vertex. + +=item B<PerformDepthFirstSearch> + + $Return = $PathsTraversal->PerformDepthFirstSearch(); + +Performs Depth First Search (DFS) and returns I<PathsTraversal>. + +=item B<PerformDepthFirstSearchWithLimit> + + $PathsTraversal->PerformDepthFirstSearchWithLimit($DepthLimit, + [$RootVertexID]); + +Performs DFS with depth up to I<DepthLimit> starting at I<RootVertexID> and returns +I<PathsTraversal>. By default, root vertex ID corresponds to an arbitrary vertex. + +=item B<PerformNeighborhoodVerticesSearch> + + $PathsTraversal->PerformNeighborhoodVerticesSearch($StartVertexID); + +Searches vertices around I<StartVertexID> at all neighborhood radii and returns +I<PathsTraversal> object. + +=item B<PerformNeighborhoodVerticesSearchWithRadiusUpto> + + $PathsTraversal->PerformNeighborhoodVerticesSearchWithRadiusUpto( + $StartVertexID, $Radius); + +Searches vertices around I<StartVertexID> with neighborhood radius up to I<Radius> and returns +I<PathsTraversal> object. + +=item B<PerformNeighborhoodVerticesSearchWithSuccessors> + + $PathsTraversal->PerformNeighborhoodVerticesSearchWithSuccessors( + $StartVertexID); + +Searches vertices around I<StartVertexID> at all neighborhood radii along with identification of +successor vertices for each vertex found during the traversal and returns I<PathsTraversal>. + +=item B<PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto> + + $PathsTraversal-> + PerformNeighborhoodVerticesSearchWithSuccessorsAndRadiusUpto( + $StartVertexID, $Radius); + +Searches vertices around I<StartVertexID> with neighborhood radius upto I<Radius> along with +identification of successor vertices for each vertex found during the traversal and returns +I<PathsTraversal>. + +=item B<PerformPathsSearch> + + $PathsTraversal->PerformPathsSearch($StartVertexID, [$AllowCycles]); + +Searches paths starting from I<StartVertexID> with no sharing of edges in paths traversed and +returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<PerformPathsSearchBetween> + + $PathsTraversal->PerformPathsSearchBetween($StartVertexID, $EndVertexID); + +Searches paths between I<StartVertexID> and I<EndVertexID> and returns I<PathsTraversal> + +=item B<PerformPathsSearchWithLength> + + $PathsTraversal->PerformPathsSearchWithLength($StartVertexID, $Length, + [$AllowCycles]); + +Searches paths starting from I<StartVertexID> with length I<Length> with no sharing of +edges in paths traversed and returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<PerformPathsSearchWithLengthUpto> + + $PathsTraversal->PerformPathsSearchWithLengthUpto($StartVertexID, $Length, + [$AllowCycles]); + +Searches paths starting from I<StartVertexID> with length upto I<Length> with no sharing of +edges in paths traversed and returns I<PathsTraversal>. + +By default, cycles are included in paths. A path containing a cycle is terminated at a vertex +completing the cycle. + +=item B<StringifyPaths> + + $String = $PathsTraversal->StringifyPaths(); + +Returns a string containing information about traversed paths in I<PathsTraversal> object + +=item B<StringifyPathsTraversal> + + $String = $PathsTraversal->StringifyPathsTraversal(); + +Returns a string containing information about I<PathsTraversal> object. + +=item B<StringifyVerticesDepth> + + $String = $PathsTraversal->StringifyVerticesDepth(); + +Returns a string containing information about depth of vertices found during search by +I<PathsTraversal> object. + +=item B<StringifyVerticesNeighborhoods> + + $String = $PathsTraversal->StringifyVerticesNeighborhoods(); + +Returns a string containing information about neighborhoods of vertices found during search by +I<PathsTraversal> object. + +=item B<StringifyVerticesNeighborhoodsWithSuccessors> + + $String = $PathsTraversal->StringifyVerticesNeighborhoodsWithSuccessors(); + +Returns a string containing information about neighborhoods of vertices along with their successors +found during search by I<PathsTraversal> object. + +=item B<StringifyVerticesPredecessors> + + $String = $PathsTraversal->StringifyVerticesPredecessors(); + +Returns a string containing information about predecessors of vertices found during search by +I<PathsTraversal> object. + +=item B<StringifyVerticesRoots> + + $String = $PathsTraversal->StringifyVerticesRoots(); + +Returns a string containing information about roots of vertices found during search by +I<PathsTraversal> object. + +=item B<StringifyVerticesSuccessors> + + $String = $PathsTraversal->StringifyVerticesSuccessors(); + +Returns a string containing information about successors of vertices found during search by +I<PathsTraversal> object. + +=back + +=head1 AUTHOR + +Manish Sud <msud@san.rr.com> + +=head1 SEE ALSO + +Graph.pm, Path.pm + +=head1 COPYRIGHT + +Copyright (C) 2015 Manish Sud. All rights reserved. + +This file is part of MayaChemTools. + +MayaChemTools is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) +any later version. + +=cut