1 package PathGraph; 2 # 3 # $RCSfile: PathGraph.pm,v $ 4 # $Date: 2008/04/19 16:11:49 $ 5 # $Revision: 1.8 $ 6 # 7 # Author: Manish Sud <msud@san.rr.com> 8 # 9 # Copyright (C) 2004-2008 Manish Sud. All rights reserved. 10 # 11 # This file is part of MayaChemTools. 12 # 13 # MayaChemTools is free software; you can redistribute it and/or modify it under 14 # the terms of the GNU Lesser General Public License as published by the Free 15 # Software Foundation; either version 3 of the License, or (at your option) any 16 # later version. 17 # 18 # MayaChemTools is distributed in the hope that it will be useful, but without 19 # any warranty; without even the implied warranty of merchantability of fitness 20 # for a particular purpose. See the GNU Lesser General Public License for more 21 # details. 22 # 23 # You should have received a copy of the GNU Lesser General Public License 24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 26 # Boston, MA, 02111-1307, USA. 27 # 28 use 5.006; 29 use strict; 30 use Carp; 31 use Exporter; 32 use Scalar::Util; 33 use Storable (); 34 use Graph; 35 use Graph::Path; 36 37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 38 39 $VERSION = '1.00'; 40 @ISA = qw(Graph Exporter); 41 @EXPORT = qw(IsPathGraph); 42 @EXPORT_OK = qw(); 43 44 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 45 46 # Setup class variables... 47 my($ClassName, $PathsPropertyName, $CyclicPathsPropertyName); 48 _InitializeClass(); 49 50 # Overload Perl functions... 51 use overload '""' => 'StringifyPathGraph'; 52 53 # Class constructor... 54 sub new { 55 my($Class, $Graph) = @_; 56 57 # Initialize object... 58 my $This = $Class->SUPER::new(); 59 bless $This, ref($Class) || $Class; 60 $This->_InitializePathGraph($Graph); 61 62 $This->_ConvertGraphIntoPathGraph($Graph); 63 64 return $This; 65 } 66 67 # Initialize object data... 68 sub _InitializePathGraph { 69 my($This, $Graph) = @_; 70 71 if (!(defined($Graph) && Graph::IsGraph($Graph))) { 72 croak "Error: ${ClassName}->new: PathGraph object can't be instantiated without a Graph object..."; 73 } 74 75 $This->{Graph} = $Graph; 76 77 return $This; 78 } 79 80 # Initialize class ... 81 sub _InitializeClass { 82 #Class name... 83 $ClassName = __PACKAGE__; 84 85 # Path edge property name... 86 $PathsPropertyName = 'Paths'; 87 88 # Cyclic path vertex property name... 89 $CyclicPathsPropertyName = 'CyclicPaths'; 90 } 91 92 # Convert graph into a path graph... 93 # 94 sub _ConvertGraphIntoPathGraph { 95 my($This, $Graph) = @_; 96 97 # Copy graph vertices and edges without any associated properties data 98 # from Graph to This: Graph properties data is available using Graph object reference 99 # store in This object... 100 # 101 $Graph->CopyVerticesAndEdges($This); 102 103 # . Attach Path property to each edge... 104 # 105 my($Index, $VertexID1, $VertexID2, $Path, @EdgesVertexIDs); 106 107 @EdgesVertexIDs = (); 108 @EdgesVertexIDs = $This->GetEdges(); 109 for ($Index = 0; $Index < $#EdgesVertexIDs; $Index += 2) { 110 $VertexID1 = $EdgesVertexIDs[$Index]; $VertexID2 = $EdgesVertexIDs[$Index + 1]; 111 $Path = new Path($VertexID1, $VertexID2); 112 my(@Paths) = (); 113 push @Paths, $Path; 114 $This->SetEdgeProperty($PathsPropertyName, \@Paths, $VertexID1, $VertexID2); 115 } 116 return $This; 117 } 118 119 # Collapse paths around a specified vertex by updating paths around the vertex 120 # and adding any resulting cyclic paths to vertices attached to specified vertex. 121 # 122 # Notes: 123 # . Path object references are stored as a list attached to Paths property on edges. 124 # Usage of list allows multiple paths attached to the egde between a pair of vertices; 125 # Graph doesn't support multiple egdes between a pair of vertices. 126 # . Cyclic path object references are stored as list on vertices as CyclicPaths graph property. 127 # List allows multiple Loop properties attached to a vertex. 128 # 129 # 130 sub CollapseVertexAndCollectCyclicPaths { 131 my($This, $VertexID) = @_; 132 133 if (!$This->HasVertex($VertexID)) { 134 carp "Warning: ${ClassName}->CollapseVertexAndCollectCyclicPaths: Didn't collapse vertex $VertexID: Vertex $VertexID doesn't exist..."; 135 return undef; 136 } 137 # Collect all paths around specified VertexID by going over paths associated with its edges... 138 my($Index, $EdgePathsRef, $EdgeVertexID1, $EdgeVertexID2, @Paths, @EdgesVertexIDs); 139 140 @EdgesVertexIDs = (); 141 @EdgesVertexIDs = $This->GetEdges($VertexID); 142 143 @Paths = (); 144 for ($Index = 0; $Index < $#EdgesVertexIDs; $Index += 2) { 145 ($EdgeVertexID1, $EdgeVertexID2) = ($EdgesVertexIDs[$Index], $EdgesVertexIDs[$Index + 1]); 146 $EdgePathsRef = $This->GetEdgeProperty($PathsPropertyName, $EdgeVertexID1, $EdgeVertexID2); 147 push @Paths, @{$EdgePathsRef}; 148 } 149 150 # Go over each pair of paths around the specified vertex, join paths and associate 151 # joined path to appropriate edge... 152 my($Index1, $Index2, $Path1, $Path2, $JoinedPath, $JoinedPathStartVertexID, $JoinedPathEndVertexID, $Path1StartVertexID, $Path1EndVertexID, $Path2StartVertexID, $Path2EndVertexID, $CommonVertices); 153 154 for ($Index1 = 0; $Index1 < $#Paths; $Index1 +=1 ) { 155 $Path1 = $Paths[$Index1]; 156 ($Path1StartVertexID, $Path1EndVertexID) = $Path1->GetTerminalVertices(); 157 158 PATH2: for ($Index2 = $Index1 + 1; $Index2 <= $#Paths; $Index2 +=1 ) { 159 $Path2 = $Paths[$Index2]; 160 ($Path2StartVertexID, $Path2EndVertexID) = $Path2->GetTerminalVertices(); 161 162 $CommonVertices = $Path1->GetCommonVertices($Path2); 163 164 # For JoinedPath to be valid cycle, Path1 and Path2 must have exactly two vertices in common. 165 # Otherwise, joined path contains duplicate vertices besides the terminal vertices and 166 # indicates a path from a different direction. 167 # 168 # For paths leading to cycles, it only makes sense to join paths with only one common vertex; 169 # otherwise, it wouldn't lead to a cycle and can be ignored. 170 # 171 if ($CommonVertices > 2) { 172 next PATH2; 173 } 174 175 $JoinedPath = $Path1->JoinAtVertex($Path2, $VertexID); 176 ($JoinedPathStartVertexID, $JoinedPathEndVertexID) = $JoinedPath->GetTerminalVertices(); 177 178 if ($JoinedPathStartVertexID == $JoinedPathEndVertexID) { 179 if (!$JoinedPath->IsIndependentCyclicPath()) { 180 next PATH2; 181 } 182 183 # It's a cycle. Attach it to the graph as CylicPaths property... 184 if ($This->HasGraphProperty($CyclicPathsPropertyName)) { 185 my($ExistingCyclicPathsRef); 186 $ExistingCyclicPathsRef = $This->GetGraphProperty($CyclicPathsPropertyName); 187 push @{$ExistingCyclicPathsRef}, $JoinedPath; 188 } 189 else { 190 my(@NewCyclicPaths) = (); 191 push @NewCyclicPaths, $JoinedPath; 192 $This->SetGraphProperty($CyclicPathsPropertyName, \@NewCyclicPaths, $JoinedPathStartVertexID); 193 } 194 } 195 else { 196 if ($This->HasEdge($JoinedPathStartVertexID, $JoinedPathEndVertexID)) { 197 # Append to the list of exisiting paths property of the edge... 198 my($ExistingPathsRef); 199 $ExistingPathsRef = $This->GetEdgeProperty($PathsPropertyName, $JoinedPathStartVertexID, $JoinedPathEndVertexID); 200 push @{$ExistingPathsRef}, $JoinedPath; 201 } 202 else { 203 # Create a new edge and associate path property... 204 my(@NewPaths) = (); 205 push @NewPaths, $JoinedPath; 206 $This->AddEdge($JoinedPathStartVertexID, $JoinedPathEndVertexID); 207 $This->SetEdgeProperty($PathsPropertyName, \@NewPaths, $JoinedPathStartVertexID, $JoinedPathEndVertexID); 208 } 209 } 210 } 211 } 212 $This->DeleteVertex($VertexID); 213 214 return $This; 215 } 216 217 # Delete vertices with degree less than a specifed degree... 218 # 219 sub DeleteVerticesWithDegreeLessThan { 220 my($This, $Degree) = @_; 221 my($VertexID, @VertexIDs); 222 223 while (@VertexIDs = $This->GetVerticesWithDegreeLessThan($Degree)) { 224 for $VertexID (@VertexIDs) { 225 $This->DeleteVertex($VertexID); 226 } 227 } 228 return $This; 229 } 230 231 # Get paths associated with edges... 232 # 233 sub GetPaths { 234 my($This) = @_; 235 my($PathsRef, @Paths, @PathsList); 236 237 @Paths = (); @PathsList = (); 238 @PathsList = $This->GetEdgesProperty($PathsPropertyName); 239 for $PathsRef (@PathsList) { 240 push @Paths, @{$PathsRef}; 241 } 242 return wantarray ? @Paths : scalar @Paths; 243 } 244 245 # Get paths associated with edges which make a cylce... 246 # 247 sub GetCyclicPaths { 248 my($This) = @_; 249 my($PathsRef, @Paths, @PathsList); 250 251 @Paths = (); @PathsList = (); 252 @PathsList = $This->GetGraphProperty($CyclicPathsPropertyName); 253 PATHS: for $PathsRef (@PathsList) { 254 if (!(defined($PathsRef) && @{$PathsRef})) { 255 next PATHS; 256 } 257 push @Paths, @{$PathsRef}; 258 } 259 return wantarray ? @Paths : scalar @Paths; 260 } 261 262 # Is it a path graph object? 263 sub IsPathGraph ($) { 264 my($Object) = @_; 265 266 return _IsPathGraph($Object); 267 } 268 269 # Return a string containg data for PathGraph object... 270 sub StringifyPathGraph { 271 my($This) = @_; 272 my($PathGraphString); 273 274 $PathGraphString = 'PathGraph:' . $This->StringifyVerticesAndEdges() . '; ' . $This->StringifyProperties(); 275 276 return $PathGraphString; 277 } 278 279 # Is it a PathGraph object? 280 sub _IsPathGraph { 281 my($Object) = @_; 282 283 return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; 284 } 285