MayaChemTools

   1 package Graph::GraphMatrix;
   2 #
   3 # File: GraphMatrix.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 Matrix;
  31 use Constants;
  32 
  33 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  34 
  35 @ISA = qw(Exporter);
  36 @EXPORT = qw();
  37 @EXPORT_OK = qw();
  38 
  39 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  40 
  41 # Setup class variables...
  42 my($ClassName);
  43 _InitializeClass();
  44 
  45 # Overload Perl functions...
  46 use overload '""' => 'StringifyGraphMatrix';
  47 
  48 # Class constructor...
  49 sub new {
  50   my($Class, $Graph) = @_;
  51 
  52   # Initialize object...
  53   my $This = {};
  54   bless $This, ref($Class) || $Class;
  55   $This->_InitializeGraphMatrix($Graph);
  56 
  57   return $This;
  58 }
  59 
  60 # Initialize object data...
  61 sub _InitializeGraphMatrix {
  62   my($This, $Graph) = @_;
  63 
  64   # Specified graph object...
  65   $This->{Graph} = $Graph;
  66 
  67   # Generated matrix...
  68   $This->{Matrix} = undef;
  69   $This->{MatrixType} = undef;
  70 
  71   # Row and columns IDs...
  72   @{$This->{RowIDs}} = ();
  73   @{$This->{ColumnIDs}} = ();
  74 
  75   return $This;
  76 }
  77 
  78 # Initialize class ...
  79 sub _InitializeClass {
  80   #Class name...
  81   $ClassName = __PACKAGE__;
  82 }
  83 
  84 # Generate the adjacency matrix for a simple graph.
  85 #
  86 # For a simple graph G with n vertices, the adjacency matrix for G is a n x n square matrix and
  87 # its elements Mij are:
  88 #
  89 #   . 0    if i == j
  90 #   . 1    if i != j and vertex Vi is adjacent to vertex Vj
  91 #   . 0    if i != j and vertex Vi is not adjacent to vertex Vj
  92 #
  93 sub GenerateAdjacencyMatrix {
  94   my($This) = @_;
  95   my($Graph, $NumOfVertices, @VertexIDs);
  96 
  97   # Get graph vertices...
  98   $Graph = $This->{Graph};
  99   @VertexIDs = $Graph->GetVertices();
 100   $NumOfVertices = scalar @VertexIDs;
 101 
 102   if ($NumOfVertices == 0) {
 103     carp "Warning: ${ClassName}->GenerateAdjacencyMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 104     return $This;
 105   }
 106 
 107   # Create adjacency matrix...
 108   my($Matrix, $RowIndex, $ColIndex, $RowVertexID, $ColVertexID, $Value, $SkipIndexCheck);
 109 
 110   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 111   $SkipIndexCheck = 1;
 112 
 113   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 114     $RowVertexID = $VertexIDs[$RowIndex];
 115     for $ColIndex (0 .. ($NumOfVertices - 1)) {
 116       $ColVertexID = $VertexIDs[$ColIndex];
 117       $Value = ($RowIndex == $ColIndex) ? 0 : ($Graph->HasEdge($RowVertexID, $ColVertexID) ? 1 : 0);
 118       $Matrix->SetValue($RowIndex, $ColIndex, $Value, $SkipIndexCheck);
 119     }
 120   }
 121   $This->_SetMatrixAndAssociatedInformation($Matrix, "AdjacencyMatrix", \@VertexIDs);
 122 
 123   return $This;
 124 }
 125 
 126 # Generate the Siedel adjacency matrix for a simple graph.
 127 #
 128 # For a simple graph G with n vertices, the Siedal adjacency matrix for G is a n x n square matrix and
 129 # its elements Mij are:
 130 #
 131 #   . 0    if i == j
 132 #   . -1   if i != j and vertex Vi is adjacent to vertex Vj
 133 #   . 1    if i != j and vertex Vi is not adjacent to vertex Vj
 134 #
 135 sub GenerateSiedelAdjacencyMatrix {
 136   my($This) = @_;
 137   my($Graph, $NumOfVertices, @VertexIDs);
 138 
 139   # Get graph vertices...
 140   $Graph = $This->{Graph};
 141   @VertexIDs = $Graph->GetVertices();
 142   $NumOfVertices = scalar @VertexIDs;
 143 
 144   if ($NumOfVertices == 0) {
 145     carp "Warning: ${ClassName}->GenerateSiedelAdjacencyMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 146     return $This;
 147   }
 148 
 149   # Create Siedel adjacency matrix...
 150   my($Matrix, $RowIndex, $ColIndex, $RowVertexID, $ColVertexID, $Value, $SkipIndexCheck);
 151 
 152   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 153   $SkipIndexCheck = 1;
 154 
 155   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 156     $RowVertexID = $VertexIDs[$RowIndex];
 157     for $ColIndex (0 .. ($NumOfVertices - 1)) {
 158       $ColVertexID = $VertexIDs[$ColIndex];
 159       $Value = ($RowIndex == $ColIndex) ? 0 : ($Graph->HasEdge($RowVertexID, $ColVertexID) ? -1 : 1);
 160       $Matrix->SetValue($RowIndex, $ColIndex, $Value, $SkipIndexCheck);
 161     }
 162   }
 163   $This->_SetMatrixAndAssociatedInformation($Matrix, "SiedelAdjacencyMatrix", \@VertexIDs);
 164 
 165   return $This;
 166 }
 167 
 168 # Generate distance matrix for a simple graph using Floyd-Marshall algorithm [Ref 67].
 169 #
 170 # For a simple graph G with n vertices, the distance matrix for G is a n x n square matrix and
 171 # its elements Mij are:
 172 #
 173 #   . 0    if i == j
 174 #   . d    if i != j and d is the shortest distance between vertex Vi and vertex Vj
 175 #
 176 # Note:
 177 #   . In the final matrix, BigNumber values correspond to vertices with no edges.
 178 #
 179 sub GenerateDistanceMatrix {
 180   my($This) = @_;
 181   my($Graph, $NumOfVertices, @VertexIDs);
 182 
 183   # Get graph vertices...
 184   $Graph = $This->{Graph};
 185   @VertexIDs = $Graph->GetVertices();
 186   $NumOfVertices = scalar @VertexIDs;
 187 
 188   if ($NumOfVertices == 0) {
 189     carp "Warning: ${ClassName}->GenerateDistanceMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 190     return $This;
 191   }
 192 
 193   # Initialize matrix...
 194   my($Matrix, $MatrixValuesRef, $RowIndex, $ColIndex, $RowVertexID, $ColVertexID, $Value);
 195 
 196   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 197   $MatrixValuesRef = $Matrix->GetMatrixValuesReference();
 198 
 199   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 200     $RowVertexID = $VertexIDs[$RowIndex];
 201     for $ColIndex (0 .. ($NumOfVertices - 1)) {
 202       $ColVertexID = $VertexIDs[$ColIndex];
 203       $Value = ($RowIndex == $ColIndex) ? 0 : ($Graph->HasEdge($RowVertexID, $ColVertexID) ? 1 : BigNumber);
 204       $MatrixValuesRef->[$RowIndex][$ColIndex] = $Value;
 205     }
 206   }
 207 
 208   # Create distance matrix...
 209   my($i, $j, $k, $Valuejk);
 210 
 211   for $i (0 .. ($NumOfVertices - 1)) {
 212     for $j (0 .. ($NumOfVertices - 1)) {
 213       for $k (0 .. ($NumOfVertices - 1)) {
 214         $Valuejk = $MatrixValuesRef->[$j][$i] + $MatrixValuesRef->[$i][$k];
 215         if ($Valuejk < $MatrixValuesRef->[$j][$k]) {
 216           $MatrixValuesRef->[$j][$k] = $Valuejk;
 217         }
 218       }
 219     }
 220   }
 221   $This->_SetMatrixAndAssociatedInformation($Matrix, "DistanceMatrix", \@VertexIDs);
 222 
 223   return $This;
 224 }
 225 
 226 # Generate the incidence matrix for a simple graph.
 227 #
 228 # For a simple graph G with n vertices and e edges, the incidence matrix for G is a n x e matrix
 229 # its elements Mij are:
 230 #
 231 #   . 1    if vertex Vi and the edge Ej are incident; in other words, Vi and Ej are related
 232 #   . 0    otherwise
 233 #
 234 sub GenerateIncidenceMatrix {
 235   my($This) = @_;
 236   my($Graph, $NumOfVertices, $NumOfEdges, @VertexIDs, @EdgeVertexIDs);
 237 
 238   # Get graph vertices and edges...
 239   $Graph = $This->{Graph};
 240   @VertexIDs = $Graph->GetVertices();
 241   $NumOfVertices = scalar @VertexIDs;
 242 
 243   @EdgeVertexIDs = $Graph->GetEdges();
 244   $NumOfEdges = @EdgeVertexIDs/2;
 245 
 246   if ($NumOfVertices == 0) {
 247     carp "Warning: ${ClassName}->GenerateIncidenceMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 248     return $This;
 249   }
 250 
 251   # Create incidence matrix...
 252   my($Matrix, $RowIndex, $ColIndex, $EdgeVertexIndex, $VertexID, $EdgeVertexID1, $EdgeVertexID2, $Value, $SkipIndexCheck);
 253 
 254   $Matrix = new Matrix($NumOfVertices, $NumOfEdges);
 255 
 256   $SkipIndexCheck = 1;
 257   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 258     $VertexID = $VertexIDs[$RowIndex];
 259     $EdgeVertexIndex = 0;
 260     for $ColIndex (0 .. ($NumOfEdges - 1)) {
 261       $EdgeVertexID1 = $EdgeVertexIDs[$EdgeVertexIndex]; $EdgeVertexIndex++;
 262       $EdgeVertexID2 = $EdgeVertexIDs[$EdgeVertexIndex]; $EdgeVertexIndex++;
 263 
 264       $Value = ($VertexID == $EdgeVertexID1 || $VertexID == $EdgeVertexID2) ? 1 : 0;
 265       $Matrix->SetValue($RowIndex, $ColIndex, $Value, $SkipIndexCheck);
 266     }
 267   }
 268   $This->_SetMatrixAndAssociatedInformation($Matrix, "IncidenceMatrix", \@VertexIDs, \@EdgeVertexIDs);
 269 
 270   return $This;
 271 }
 272 
 273 # Generate the degree matrix for a simple graph.
 274 #
 275 # For a simple graph G with n vertices, the degree matrix for G is a n x n square matrix and
 276 # its elements Mij are:
 277 #
 278 #   . deg(Vi)   if i == j and deg(Vi) is the degree of vertex Vi
 279 #   . 0         otherwise
 280 #
 281 sub GenerateDegreeMatrix {
 282   my($This) = @_;
 283   my($Graph, $NumOfVertices, @VertexIDs);
 284 
 285   # Get graph vertices...
 286   $Graph = $This->{Graph};
 287   @VertexIDs = $Graph->GetVertices();
 288   $NumOfVertices = scalar @VertexIDs;
 289 
 290   if ($NumOfVertices == 0) {
 291     carp "Warning: ${ClassName}->GenerateDegreeMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 292     return $This;
 293   }
 294 
 295   # Create degree matrix...
 296   my($Matrix, $Index, $VertexID, $Degree, $SkipIndexCheck);
 297 
 298   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 299   $SkipIndexCheck = 1;
 300 
 301   for $Index (0 .. ($NumOfVertices - 1)) {
 302     $VertexID = $VertexIDs[$Index];
 303     $Degree = $Graph->GetDegree($VertexID);
 304     $Matrix->SetValue($Index, $Index, $Degree, $SkipIndexCheck);
 305   }
 306   $This->_SetMatrixAndAssociatedInformation($Matrix, "DegreeMatrix", \@VertexIDs);
 307 
 308   return $This;
 309 }
 310 
 311 # Generate the Laplacian matrix for a simple graph.
 312 #
 313 # For a simple graph G with n vertices, the Laplacian matrix for G is a n x n square matrix and
 314 # its elements Mij are:
 315 #
 316 #   . deg(Vi)   if i == j and deg(Vi) is the degree of vertex Vi
 317 #   . -1        if i != j and vertex Vi is adjacent to vertex Vj
 318 #   . 0         otherwise
 319 #
 320 # Note: The Laplacian matrix is the difference between the degree matrix and adjacency matrix.
 321 #
 322 sub GenerateLaplacianMatrix {
 323   my($This) = @_;
 324   my($Graph, $NumOfVertices, @VertexIDs);
 325 
 326   # Get graph vertices...
 327   $Graph = $This->{Graph};
 328   @VertexIDs = $Graph->GetVertices();
 329   $NumOfVertices = scalar @VertexIDs;
 330 
 331   if ($NumOfVertices == 0) {
 332     carp "Warning: ${ClassName}->GenerateLaplacianMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 333     return $This;
 334   }
 335 
 336   # Create adjacency matrix...
 337   my($Matrix, $RowIndex, $ColIndex, $RowVertexID, $ColVertexID, $Value, $SkipIndexCheck);
 338 
 339   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 340   $SkipIndexCheck = 1;
 341 
 342   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 343     $RowVertexID = $VertexIDs[$RowIndex];
 344     for $ColIndex (0 .. ($NumOfVertices - 1)) {
 345       $ColVertexID = $VertexIDs[$ColIndex];
 346       $Value = ($RowIndex == $ColIndex) ? $Graph->GetDegree($RowVertexID) : ($Graph->HasEdge($RowVertexID, $ColVertexID) ? -1 : 0);
 347       $Matrix->SetValue($RowIndex, $ColIndex, $Value, $SkipIndexCheck);
 348     }
 349   }
 350   $This->_SetMatrixAndAssociatedInformation($Matrix, "LaplacianMatrix", \@VertexIDs);
 351 
 352   return $This;
 353 }
 354 
 355 # Generate the normalized Laplacian matrix for a simple graph.
 356 #
 357 # For a simple graph G with n vertices, the normalized Laplacian matrix L for G is a n x n square matrix and
 358 # its elements Lij are:
 359 #
 360 #   . 1                           if i == j and deg(Vi) != 0
 361 #   . -1/SQRT(deg(Vi) * deg(Vj))  if i != j and vertex Vi is adjacent to vertex Vj
 362 #   . 0                           otherwise
 363 #
 364 #
 365 sub GenerateNormalizedLaplacianMatrix {
 366   my($This) = @_;
 367   my($Graph, $NumOfVertices, @VertexIDs);
 368 
 369   # Get graph vertices...
 370   $Graph = $This->{Graph};
 371   @VertexIDs = $Graph->GetVertices();
 372   $NumOfVertices = scalar @VertexIDs;
 373 
 374   if ($NumOfVertices == 0) {
 375     carp "Warning: ${ClassName}->GenerateNormalizedLaplacianMatrix: Specified graph doesn't contain any vertices: No matrix generated...";
 376     return $This;
 377   }
 378 
 379   # Create adjacency matrix...
 380   my($Matrix, $RowIndex, $ColIndex, $RowVertexID, $ColVertexID, $RowVertexDegree, $ColVertexDegree, $Value, $SkipIndexCheck);
 381 
 382   $Matrix = new Matrix($NumOfVertices, $NumOfVertices);
 383   $SkipIndexCheck = 1;
 384 
 385   for $RowIndex (0 .. ($NumOfVertices - 1)) {
 386     $RowVertexID = $VertexIDs[$RowIndex];
 387     $RowVertexDegree = $Graph->GetDegree($RowVertexID);
 388     for $ColIndex (0 .. ($NumOfVertices - 1)) {
 389       $ColVertexID = $VertexIDs[$ColIndex];
 390       $Value = 0;
 391       if ($RowIndex == $ColIndex) {
 392         $Value = ($RowVertexDegree == 0) ? 0 : 1;
 393       }
 394       else {
 395         $ColVertexDegree = $Graph->GetDegree($ColVertexID);
 396         $Value = $Graph->HasEdge($RowVertexID, $ColVertexID) ? (-1/sqrt($RowVertexDegree * $ColVertexDegree)) : 0;
 397       }
 398       $Matrix->SetValue($RowIndex, $ColIndex, $Value, $SkipIndexCheck);
 399     }
 400   }
 401   $This->_SetMatrixAndAssociatedInformation($Matrix, "NormalizedLaplacianMatrix", \@VertexIDs);
 402 
 403   return $This;
 404 }
 405 
 406 # Generate the admittance matrix for a simple graph.
 407 #
 408 sub GenerateAdmittanceMatrix {
 409   my($This) = @_;
 410 
 411   $This->GenerateLaplacianMatrix();
 412   $This->{MatrixType} = "AdmittanceMatrix";
 413 
 414   return $This;
 415 }
 416 
 417 # Generate the Kirchhoff matrix for a simple graph.
 418 #
 419 sub GenerateKirchhoffMatrix {
 420   my($This) = @_;
 421 
 422   $This->GenerateLaplacianMatrix();
 423   $This->{MatrixType} = "KirchhoffMatrix";
 424 
 425   return $This;
 426 }
 427 
 428 # Get generated matrix...
 429 #
 430 sub GetMatrix {
 431   my($This) = @_;
 432 
 433   return $This->{Matrix};
 434 }
 435 
 436 # Get matrix type...
 437 #
 438 sub GetMatrixType {
 439   my($This) = @_;
 440 
 441   return $This->{MatrixType};
 442 }
 443 
 444 # Get row IDs...
 445 #
 446 sub GetRowIDs {
 447   my($This) = @_;
 448 
 449   return @{$This->{RowIDs}};
 450 }
 451 
 452 # Get column IDs...
 453 #
 454 sub GetColumnIDs {
 455   my($This) = @_;
 456 
 457   return @{$This->{ColumnIDs}};
 458 }
 459 
 460 
 461 # Setup matrix and other associated information...
 462 #
 463 sub _SetMatrixAndAssociatedInformation {
 464   my($This, $Matrix, $MatrixType, $VertexIDsRef, $EdgeVertexIDsRef) = @_;
 465 
 466   $This->{Matrix} = $Matrix;
 467   $This->{MatrixType} = $MatrixType;
 468 
 469   @{$This->{RowIDs}} = ();
 470   @{$This->{ColumnIDs}} = ();
 471 
 472   @{$This->{RowIDs}} = @{$VertexIDsRef};
 473 
 474   if ($MatrixType =~ /^IncidenceMatrix$/i) {
 475     # Setup column IDs using edge vertex IDs...
 476     my($NumOfEdges, $EdgeVertexIndex, $ColIndex, $EdgeVertexID1, $EdgeVertexID2, $EdgeID);
 477 
 478     $NumOfEdges = (scalar @{$EdgeVertexIDsRef})/2;
 479     $EdgeVertexIndex = 0;
 480 
 481     for $ColIndex (0 .. ($NumOfEdges - 1)) {
 482       $EdgeVertexID1 = $EdgeVertexIDsRef->[$EdgeVertexIndex]; $EdgeVertexIndex++;
 483       $EdgeVertexID2 = $EdgeVertexIDsRef->[$EdgeVertexIndex]; $EdgeVertexIndex++;
 484       $EdgeID = ($EdgeVertexID1 < $EdgeVertexID2) ? "${EdgeVertexID1}-${EdgeVertexID2}" : "${EdgeVertexID2}-${EdgeVertexID1}";
 485 
 486       push @{$This->{ColumnIDs}}, $EdgeID;
 487     }
 488   }
 489   else {
 490     push @{$This->{ColumnIDs}}, @{$VertexIDsRef};
 491   }
 492   return $This;
 493 }
 494 
 495 # Return a string containg data for GraphMatrix object...
 496 sub StringifyGraphMatrix {
 497   my($This) = @_;
 498   my($GraphMatrixString, $RowIDs);
 499 
 500   $GraphMatrixString = "GraphMatrix:";
 501 
 502   $GraphMatrixString .= (defined $This->{MatrixType}) ? " MatrixType: $This->{MatrixType}" : "MatrixType: <Undefined>";
 503   $GraphMatrixString .= (scalar @{$This->{RowIDs}}) ? "; RowIDs: @{$This->{RowIDs}}" : "; RowIDs: <Undefined>";
 504   $GraphMatrixString .= (scalar @{$This->{ColumnIDs}}) ? "; ColumnIDs: @{$This->{ColumnIDs}}" : "; ColumnIDs: <Undefined>";
 505 
 506   $GraphMatrixString .= (defined $This->{Matrix}) ? "; Matrix: $This->{Matrix}" : "; Matrix: <Undefined>";
 507 
 508   return $GraphMatrixString;
 509 }
 510