MayaChemTools

   1 package Molecule;
   2 #
   3 # $RCSfile: Molecule.pm,v $
   4 # $Date: 2011/12/26 21:53:29 $
   5 # $Revision: 1.47 $
   6 #
   7 # Author: Manish Sud <msud@san.rr.com>
   8 #
   9 # Copyright (C) 2004-2012 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 
  29 use strict;
  30 use Carp;
  31 use Exporter;
  32 use Storable ();
  33 use Scalar::Util ();
  34 use ObjectProperty;
  35 use Graph;
  36 use Atom;
  37 use Bond;
  38 use MolecularFormula;
  39 use MathUtil;
  40 use PeriodicTable;
  41 
  42 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  43 
  44 @ISA = qw(Graph ObjectProperty Exporter);
  45 @EXPORT = qw(IsMolecule);
  46 @EXPORT_OK = qw(FormatElementalCompositionInformation);
  47 
  48 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  49 
  50 # Setup class variables...
  51 my($ClassName, $ObjectID);
  52 _InitializeClass();
  53 
  54 # Overload Perl functions...
  55 use overload '""' => 'StringifyMolecule';
  56 
  57 # Class constructor...
  58 sub new {
  59   my($Class, %NamesAndValues) = @_;
  60 
  61   # Initialize object...
  62   my $This = $Class->SUPER::new();
  63   bless $This, ref($Class) || $Class;
  64   $This->_InitializeMolecule();
  65 
  66   if (keys %NamesAndValues) { $This->_InitializeMoleculeProperties(%NamesAndValues); }
  67 
  68   return $This;
  69 }
  70 
  71 # Initialize object data...
  72 sub _InitializeMolecule {
  73   my($This) = @_;
  74   my($ObjectID) = _GetNewObjectID();
  75 
  76   # All other property names and values along with all Set/Get<PropertyName> methods
  77   # are implemented on-demand using ObjectProperty class.
  78   $This->{ID} = $ObjectID;
  79   $This->{Name} = "Molecule ${ObjectID}";
  80 }
  81 
  82 # Initialize molecule properties...
  83 sub _InitializeMoleculeProperties {
  84   my($This, %NamesAndValues) = @_;
  85 
  86   my($Name, $Value, $MethodName);
  87   while (($Name, $Value) = each  %NamesAndValues) {
  88     $MethodName = "Set${Name}";
  89     $This->$MethodName($Value);
  90   }
  91 }
  92 
  93 # Initialize class ...
  94 sub _InitializeClass {
  95   #Class name...
  96   $ClassName = __PACKAGE__;
  97 
  98   # ID to keep track of objects...
  99   $ObjectID = 0;
 100 }
 101 
 102 # Setup an explicit SetID method to block setting of ID by AUTOLOAD function...
 103 sub SetID {
 104   my($This, $Value) = @_;
 105 
 106   carp "Warning: ${ClassName}->SetID: Object ID can't be changed: it's used for internal tracking...";
 107 
 108   return $This;
 109 }
 110 
 111 # Add an atom...
 112 sub AddAtom {
 113   my($This, $Atom) = @_;
 114 
 115   if (!defined $Atom) {
 116     carp "Warning: ${ClassName}->AddAtom: No atom added: Atom must be specified...";
 117     return undef;
 118   }
 119   if ($This->HasAtom($Atom)) {
 120     carp "Warning: ${ClassName}->AddAtom: No atom added: Atom already exists...";
 121     return undef;
 122   }
 123   return $This->_AddAtom($Atom);
 124 }
 125 
 126 # Add an atom...
 127 sub _AddAtom {
 128   my($This, $Atom) = @_;
 129 
 130   # Assign atom to this molecule...
 131   $Atom->_SetMolecule($This);
 132 
 133   # Add it to the graph as a vertex...
 134   my($AtomID);
 135   $AtomID = $Atom->GetID();
 136   $This->AddVertex($AtomID);
 137   $This->SetVertexProperty('Atom', $Atom, $AtomID);
 138 
 139   return $This;
 140 }
 141 
 142 # Add atoms...
 143 sub AddAtoms {
 144   my($This, @Atoms) = @_;
 145 
 146   if (!@Atoms) {
 147     carp "Warning: ${ClassName}->AddAtoms: No atoms added: Atoms list must be specified...";
 148     return undef;
 149   }
 150   my($Atom);
 151   for $Atom (@Atoms) {
 152     $This->AddAtom($Atom);
 153   }
 154   return $This;
 155 }
 156 
 157 # Create an atom and add it to molecule...
 158 sub NewAtom {
 159   my($This, %NamesAndValues) = @_;
 160   my($Atom);
 161 
 162   $Atom = new Atom(%NamesAndValues);
 163   $This->AddAtom($Atom);
 164 
 165   return $Atom;
 166 }
 167 
 168 # Delete an atom...
 169 sub DeleteAtom {
 170   my($This, $Atom) = @_;
 171 
 172   if (!defined $Atom) {
 173     carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom must be specified...";
 174     return undef;
 175   }
 176   # Does the atom exist in  molecule?
 177   if (!$This->HasAtom($Atom)) {
 178     carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom doesn't exist...";
 179     return undef;
 180   }
 181   return $This->_DeleteAtom($Atom);
 182 }
 183 
 184 # Delete atom...
 185 sub _DeleteAtom {
 186   my($This, $Atom) = @_;
 187 
 188   my($AtomID);
 189   $AtomID = $Atom->GetID();
 190   $This->DeleteVertex($AtomID);
 191 
 192   return $This;
 193 }
 194 
 195 # Delete atoms...
 196 sub DeleteAtoms {
 197   my($This, @Atoms) = @_;
 198 
 199   if (!@Atoms) {
 200     carp "Warning: ${ClassName}->DeleteAtoms: No atoms added: Atoms list must be specified...";
 201     return undef;
 202   }
 203   my($Atom);
 204   for $Atom (@Atoms) {
 205     $This->DeleteAtom($Atom);
 206   }
 207 
 208   return $This;
 209 }
 210 
 211 # Is this atom present?
 212 sub HasAtom {
 213   my($This, $Atom) = @_;
 214 
 215   if (!defined $Atom) {
 216     return 0;
 217   }
 218   if (!$Atom->HasProperty('Molecule')) {
 219     # It's not in molecule...
 220     return 0;
 221   }
 222   my($AtomID);
 223   $AtomID = $Atom->GetID();
 224   if (!$This->HasVertex($AtomID)) {
 225     # It's not in molecule...
 226     return 0;
 227   }
 228   my($Molecule);
 229   $Molecule = $Atom->GetProperty('Molecule');
 230 
 231   return ($This->HasVertex($AtomID) && $This->GetID() == $Molecule->GetID()) ? 1 : 0;
 232 }
 233 
 234 # Return an array of atoms. In scalar context,  it returns number of atoms. Additionally,
 235 # atoms array can be filtered by any user specifiable Atom class method...
 236 #
 237 sub GetAtoms {
 238   my($This, $AtomCheckMethodName, $NegateMethodResult) = @_;
 239   my(@Atoms, @AtomIDs);
 240 
 241   @Atoms = (); @AtomIDs = ();
 242 
 243   @AtomIDs = $This->GetVertices();
 244   if (!@AtomIDs) {
 245     return wantarray ? @Atoms : scalar @Atoms;
 246   }
 247 
 248   @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs);
 249 
 250   if (!defined $AtomCheckMethodName) {
 251     return wantarray ? @Atoms : scalar @Atoms;
 252   }
 253   $NegateMethodResult = (defined($NegateMethodResult) &&  $NegateMethodResult) ? 1 : 0;
 254 
 255   # Filter out atoms...
 256   my($Atom, $KeepAtom, @FilteredAtoms);
 257   @FilteredAtoms = ();
 258   for $Atom (@Atoms) {
 259     $KeepAtom = $NegateMethodResult ? (!$Atom->$AtomCheckMethodName()) : $Atom->$AtomCheckMethodName();
 260     if ($KeepAtom) {
 261       push @FilteredAtoms, $Atom;
 262     }
 263   }
 264   return wantarray ? @FilteredAtoms : scalar @FilteredAtoms;
 265 }
 266 
 267 # Return an array of bonds. In scalar context, it returns number of bonds...
 268 sub GetBonds {
 269   my($This) = @_;
 270   my(@Bonds, @EdgesAtomsIDs);
 271 
 272   @Bonds = (); @EdgesAtomsIDs = ();
 273 
 274   @EdgesAtomsIDs = $This->GetEdges();
 275   if (@EdgesAtomsIDs) {
 276     @Bonds = $This->GetEdgesProperty('Bond', @EdgesAtomsIDs);
 277   }
 278   return wantarray ? @Bonds : scalar @Bonds;
 279 }
 280 
 281 # Get number of atoms in molecule...
 282 sub GetNumOfAtoms {
 283   my($This) = @_;
 284   my($NumOfAtoms);
 285 
 286   $NumOfAtoms = $This->GetAtoms();
 287 
 288   return $NumOfAtoms;
 289 }
 290 
 291 # Get number of bonds in molecule...
 292 sub GetNumOfBonds {
 293   my($This) = @_;
 294   my($NumOfBonds);
 295 
 296   $NumOfBonds = $This->GetBonds();
 297 
 298   return $NumOfBonds;
 299 }
 300 
 301 # Get number of heavy atoms in molecule...
 302 sub GetNumOfHeavyAtoms {
 303   my($This) = @_;
 304 
 305   return $This->GetNumOfNonHydrogenAtoms();
 306 }
 307 
 308 # Get number of non-hydrogen atoms in molecule...
 309 sub GetNumOfNonHydrogenAtoms {
 310   my($This) = @_;
 311   my($NumOfNonHydrogenAtoms, $Atom, @Atoms);
 312 
 313   @Atoms = $This->GetAtoms();
 314   $NumOfNonHydrogenAtoms = 0;
 315   for $Atom (@Atoms) {
 316     if (!$Atom->IsHydrogen()) {
 317       $NumOfNonHydrogenAtoms++;
 318     }
 319   }
 320   return $NumOfNonHydrogenAtoms;
 321 }
 322 
 323 # Get number of hydrogen atoms in molecule...
 324 sub GetNumOfHydrogenAtoms {
 325   my($This) = @_;
 326   my($NumOfHydrogenAtoms, $Atom, @Atoms);
 327 
 328   @Atoms = $This->GetAtoms();
 329   $NumOfHydrogenAtoms = 0;
 330   for $Atom (@Atoms) {
 331     if ($Atom->IsHydrogen()) {
 332       $NumOfHydrogenAtoms++;
 333     }
 334   }
 335   return $NumOfHydrogenAtoms;
 336 }
 337 
 338 # Get number of missing hydrogen atoms in molecule...
 339 sub GetNumOfMissingHydrogenAtoms {
 340   my($This) = @_;
 341   my($NumOfMissingHydrogenAtoms, $Atom, @Atoms);
 342 
 343   @Atoms = $This->GetAtoms();
 344   $NumOfMissingHydrogenAtoms = 0;
 345   for $Atom (@Atoms) {
 346     if (!$Atom->IsHydrogen()) {
 347       $NumOfMissingHydrogenAtoms += $Atom->GetNumOfMissingHydrogens();
 348     }
 349   }
 350   return $NumOfMissingHydrogenAtoms;
 351 }
 352 
 353 # Add bond...
 354 sub AddBond {
 355   my($This, $Bond) = @_;
 356   my($Atom1, $Atom2);
 357 
 358   ($Atom1, $Atom2) = $Bond->GetAtoms();
 359   if (!(defined($Atom1) && defined($Atom2))) {
 360     carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be specified...";
 361     return undef;
 362   }
 363   if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) {
 364     carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be present...";
 365     return undef;
 366   }
 367   if ($This->HasBond($Bond)) {
 368     carp "Warning: ${ClassName}->AddBond: No bond added: Bond already exists...";
 369     return undef;
 370   }
 371   return $This->_AddBond($Bond);
 372 }
 373 
 374 # Add bond...
 375 sub _AddBond {
 376   my($This, $Bond) = @_;
 377 
 378   # Assign bond to this molecule...
 379   $Bond->_SetMolecule($This);
 380 
 381   # Add it to the graph as an edge...
 382   my($Atom1, $Atom2, $AtomID1, $AtomID2);
 383   ($Atom1, $Atom2) = $Bond->GetAtoms();
 384   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID();
 385   $This->AddEdge($AtomID1, $AtomID2);
 386   $This->SetEdgeProperty('Bond', $Bond, $AtomID1, $AtomID2);
 387 
 388   return $This;
 389 }
 390 
 391 # Add Bonds...
 392 sub AddBonds {
 393   my($This, @Bonds) = @_;
 394 
 395   if (!@Bonds) {
 396     carp "Warning: ${ClassName}->AddBonds: No bonds added: Bonds list must be specified...";
 397     return undef;
 398   }
 399   my($Bond);
 400   for $Bond (@Bonds) {
 401     $This->AddBond($Bond);
 402   }
 403   return $This;
 404 }
 405 
 406 # Create a bond and add it to molecule...
 407 sub NewBond {
 408   my($This, %NamesAndValues) = @_;
 409   my($Bond);
 410 
 411   $Bond = new Bond(%NamesAndValues);
 412   $This->AddBond($Bond);
 413 
 414   return $Bond;
 415 }
 416 
 417 # Delete a bond...
 418 sub DeleteBond {
 419   my($This, $Bond) = @_;
 420 
 421   if (!defined $Bond) {
 422     carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond must be specified...";
 423     return undef;
 424   }
 425   # Does the bond exist in molecule?
 426   if (!$This->HasBond($Bond)) {
 427     carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond doesn't exist...";
 428     return undef;
 429   }
 430   return $This->_DeleteBond($Bond);
 431 }
 432 
 433 # Delete bond...
 434 sub _DeleteBond {
 435   my($This, $Bond) = @_;
 436 
 437   my($Atom1, $Atom2, $AtomID1, $AtomID2);
 438   ($Atom1, $Atom2) = $Bond->GetAtoms();
 439   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID();
 440   $This->DeleteEdge($AtomID1, $AtomID2);
 441 
 442   return $This;
 443 }
 444 
 445 # Delete bonds...
 446 sub DeleteBonds {
 447   my($This, @Bonds) = @_;
 448 
 449   if (!@Bonds) {
 450     carp "Warning: ${ClassName}->DeleteBonds: No bonds added: Bonds list must be specified...";
 451     return undef;
 452   }
 453   my($Bond);
 454   for $Bond (@Bonds) {
 455     $This->DeleteBond($Bond);
 456   }
 457 
 458   return $This;
 459 }
 460 
 461 # Has bond...
 462 sub HasBond {
 463   my($This, $Bond) = @_;
 464   my($Atom1, $Atom2);
 465 
 466   ($Atom1, $Atom2) = $Bond->GetAtoms();
 467   if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) {
 468     return 0;
 469   }
 470   if (!$Bond->HasProperty('Molecule')) {
 471     # It's not in molecule...
 472     return 0;
 473   }
 474   my($AtomID1, $AtomID2, $Molecule);
 475   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID();
 476   $Molecule = $Bond->GetMolecule();
 477 
 478   return ($This->HasEdge($AtomID1, $AtomID2) && $This->GetID() == $Molecule->GetID()) ? 1 : 0;
 479 }
 480 
 481 # Get atom neighbors...
 482 sub _GetAtomNeighbors {
 483   my($This, $Atom) = @_;
 484 
 485   my($AtomID, @Atoms, @AtomIDs);
 486 
 487   @Atoms = (); @AtomIDs = ();
 488   $AtomID = $Atom->GetID();
 489   @AtomIDs = $This->GetNeighbors($AtomID);
 490   if (@AtomIDs) {
 491     @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs);
 492   }
 493   return wantarray ? @Atoms : scalar @Atoms;
 494 }
 495 
 496 # Get atom bonds...
 497 sub _GetAtomBonds {
 498   my($This, $Atom) = @_;
 499   my($AtomID, @AtomIDs, @Bonds);
 500 
 501   @Bonds = (); @AtomIDs = ();
 502   $AtomID = $Atom->GetID();
 503   @AtomIDs = $This->GetEdges($AtomID);
 504   if (@AtomIDs) {
 505     @Bonds = $This->GetEdgesProperty('Bond', @AtomIDs);
 506   }
 507   return wantarray ? @Bonds : scalar @Bonds;
 508 }
 509 
 510 # Get bond to specified atom...
 511 sub _GetBondToAtom {
 512   my($This, $Atom1, $Atom2) = @_;
 513   my($AtomID1, $AtomID2);
 514 
 515   $AtomID1 = $Atom1->GetID();
 516   $AtomID2 = $Atom2->GetID();
 517 
 518   return $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2);
 519 }
 520 
 521 # Add hydrogens to each atoms in molecule and return total number of hydrogens added...
 522 sub AddHydrogens {
 523   my($This) = @_;
 524 
 525   return $This->_AddHydrogens();
 526 }
 527 
 528 # Add hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens added...
 529 sub AddPolarHydrogens {
 530   my($This) = @_;
 531   my($PolarHydrogensOnly) = 1;
 532 
 533   return $This->_AddHydrogens($PolarHydrogensOnly);
 534 }
 535 
 536 # Add all the hydrogens or hydrogens for polar atoms only...
 537 #
 538 # Note:
 539 #   . The current release of MayaChemTools doesn't assign any hydrogen positions.
 540 #
 541 sub _AddHydrogens {
 542   my($This, $PolarHydrogensOnly) = @_;
 543   my($Atom, $NumOfHydrogensAdded, $HydrogenPositionsWarning, @Atoms);
 544 
 545   if (! defined $PolarHydrogensOnly) {
 546     $PolarHydrogensOnly = 0;
 547   }
 548 
 549   $NumOfHydrogensAdded = 0;
 550   @Atoms = $This->GetAtoms();
 551   $HydrogenPositionsWarning = 0;
 552 
 553   ATOM: for $Atom (@Atoms) {
 554     if ($PolarHydrogensOnly) {
 555       if (!$Atom->IsPolarAtom()) {
 556         next ATOM;
 557       }
 558     }
 559     $NumOfHydrogensAdded += $Atom->AddHydrogens($HydrogenPositionsWarning);
 560   }
 561   return $NumOfHydrogensAdded;
 562 }
 563 
 564 # Delete all hydrogens atoms in molecule and return total number of hydrogens removed...
 565 sub DeleteHydrogens {
 566   my($This) = @_;
 567 
 568   return $This->_DeleteHydrogens();
 569 }
 570 
 571 # Delete hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens removed...
 572 sub DeletePolarHydrogens {
 573   my($This) = @_;
 574   my($PolarHydrogensOnly) = 1;
 575 
 576   return $This->_DeleteHydrogens($PolarHydrogensOnly);
 577 }
 578 
 579 # Delete all hydrogens atoms in molecule and return total number of hydrogens removed...
 580 sub _DeleteHydrogens {
 581   my($This, $PolarHydrogensOnly) = @_;
 582   my($Atom, $NumOfHydrogensRemoved, @Atoms);
 583 
 584   if (! defined $PolarHydrogensOnly) {
 585     $PolarHydrogensOnly = 0;
 586   }
 587 
 588   $NumOfHydrogensRemoved = 0;
 589   @Atoms = $This->GetAtoms();
 590 
 591   ATOM: for $Atom (@Atoms) {
 592     if ($PolarHydrogensOnly) {
 593       if (!$Atom->IsPolarHydrogen()) {
 594         next ATOM;
 595       }
 596     }
 597     elsif (!$Atom->IsHydrogen()) {
 598       next ATOM;
 599     }
 600     $This->_DeleteAtom($Atom);
 601     $NumOfHydrogensRemoved++;
 602   }
 603   return $NumOfHydrogensRemoved;
 604 }
 605 
 606 # Get molecular weight by summing up atomic weights of all the atoms...
 607 sub GetMolecularWeight {
 608   my($This, $IncludeMissingHydrogens) = @_;
 609   my($MolecularWeight, $AtomicWeight, @Atoms, $Atom);
 610 
 611   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1;
 612 
 613   $MolecularWeight = 0;
 614   @Atoms = $This->GetAtoms();
 615   for $Atom (@Atoms) {
 616     $AtomicWeight = $Atom->GetAtomicWeight();
 617     if (defined $AtomicWeight) {
 618       $MolecularWeight += $AtomicWeight;
 619     }
 620   }
 621 
 622   if (!$IncludeMissingHydrogens) {
 623     return $MolecularWeight;
 624   }
 625 
 626   # Account for missing hydrogen atoms...
 627   my($NumOfMissingHydrogenAtoms);
 628 
 629   $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms();
 630   if ($NumOfMissingHydrogenAtoms) {
 631     $MolecularWeight += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementAtomicWeight('H');
 632   }
 633 
 634   return $MolecularWeight;
 635 }
 636 
 637 # Get exact mass by summing up exact masses of all the atoms...
 638 sub GetExactMass {
 639   my($This, $IncludeMissingHydrogens) = @_;
 640   my($ExactMass, $AtomicMass, @Atoms, $Atom);
 641 
 642   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1;
 643 
 644   $ExactMass = 0;
 645   @Atoms = $This->GetAtoms();
 646   for $Atom (@Atoms) {
 647     $AtomicMass = $Atom->GetExactMass();
 648     if (defined $AtomicMass) {
 649       $ExactMass += $AtomicMass;
 650     }
 651   }
 652 
 653   if (!$IncludeMissingHydrogens) {
 654     return $ExactMass;
 655   }
 656 
 657   # Account for missing hydrogen atoms...
 658   my($NumOfMissingHydrogenAtoms);
 659 
 660   $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms();
 661   if ($NumOfMissingHydrogenAtoms) {
 662     $ExactMass += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementMostAbundantNaturalIsotopeMass('H');
 663   }
 664 
 665   return $ExactMass;
 666 }
 667 
 668 # Get net formal charge on the molecule using one of the following two methods:
 669 #   . Using explicitly set FormalCharge property
 670 #   . Adding up formal charge on each atom in the molecule
 671 #
 672 # Caveats:
 673 #   . FormalCharge is different from Charge property of the molecule which corresponds to
 674 #     sum of partial atomic charges explicitly set for each atom using a specific methodology.
 675 #
 676 sub GetFormalCharge {
 677   my($This) = @_;
 678 
 679   # Is FormalCharge property explicitly set?
 680   if ($This->HasProperty('FormalCharge')) {
 681     return $This->GetProperty('FormalCharge');
 682   }
 683   my($FormalCharge, $AtomicFormalCharge, @Atoms, $Atom);
 684 
 685   $FormalCharge = 0;
 686   @Atoms = $This->GetAtoms();
 687   for $Atom (@Atoms) {
 688     $AtomicFormalCharge = $Atom->GetFormalCharge();
 689     if (defined $AtomicFormalCharge) {
 690       $FormalCharge += $AtomicFormalCharge;
 691     }
 692   }
 693   return $FormalCharge;
 694 }
 695 
 696 # Get net charge on the molecule using one of the following two methods:
 697 #   . Using explicitly set Charge property
 698 #   . Adding up charge on each atom in the molecule
 699 #
 700 # Caveats:
 701 #   . FormalCharge is different from Charge property of the molecule which corresponds to
 702 #     sum of partial atomic charges explicitly set for each atom using a specific methodology.
 703 #
 704 sub GetCharge {
 705   my($This) = @_;
 706 
 707   # Is Charge property explicitly set?
 708   if ($This->HasProperty('Charge')) {
 709     return $This->GetProperty('Charge');
 710   }
 711   my($Charge, $AtomicCharge, @Atoms, $Atom);
 712 
 713   $Charge = 0;
 714   @Atoms = $This->GetAtoms();
 715   for $Atom (@Atoms) {
 716     $AtomicCharge = $Atom->GetCharge();
 717     if (defined $AtomicCharge) {
 718       $Charge += $AtomicCharge;
 719     }
 720   }
 721   return $Charge;
 722 }
 723 
 724 # Get net SpinMultiplicity on the molecule using one of the following two methods:
 725 #   . Using explicitly set SpinMultiplicity property
 726 #   . Adding up SpinMultiplicity on each atom in the molecule
 727 #
 728 #
 729 sub GetSpinMultiplicity {
 730   my($This) = @_;
 731 
 732   # Is SpinMultiplicity property explicitly set?
 733   if ($This->HasProperty('SpinMultiplicity')) {
 734     return $This->GetProperty('SpinMultiplicity');
 735   }
 736   my($AtomicSpinMultiplicity, $SpinMultiplicity, @Atoms, $Atom);
 737 
 738   $SpinMultiplicity = 0;
 739   @Atoms = $This->GetAtoms();
 740   for $Atom (@Atoms) {
 741     $AtomicSpinMultiplicity = $Atom->GetSpinMultiplicity();
 742     if (defined $AtomicSpinMultiplicity) {
 743       $SpinMultiplicity += $AtomicSpinMultiplicity;
 744     }
 745   }
 746   return $SpinMultiplicity;
 747 }
 748 
 749 # Get molecular formula by collecting information about all atoms in the molecule and
 750 # composing the formula using Hills ordering system:
 751 #   . C shows up first and H follows assuming C is present
 752 #   . All other standard elements are sorted alphanumerically
 753 #   . All other non-stanard atom symbols are also sorted alphanumerically and follow standard elements
 754 #
 755 # Caveats:
 756 #   . By default, missing hydrogens and nonelements are also included
 757 #   . Elements for disconnected fragments are combined into the same formula
 758 #
 759 sub GetMolecularFormula {
 760   my($This, $IncludeMissingHydrogens, $IncludeNonElements) = @_;
 761   my($MolecularFormula, $AtomSymbol, $ElementsCountRef, $NonElementsCountRef);
 762 
 763   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1;
 764   $IncludeNonElements = defined($IncludeNonElements) ? $IncludeNonElements : 1;
 765 
 766   # Get elements count and setup molecular formula...
 767   ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens);
 768   $MolecularFormula = '';
 769 
 770   # Count C and H first...
 771   if (exists $ElementsCountRef->{C} ) {
 772     $MolecularFormula .= 'C' . ($ElementsCountRef->{C} > 1 ? $ElementsCountRef->{C} : '');
 773     delete $ElementsCountRef->{C};
 774 
 775     if (exists $ElementsCountRef->{H} ) {
 776       $MolecularFormula .= 'H' . ($ElementsCountRef->{H} > 1 ? $ElementsCountRef->{H} : '');
 777       delete $ElementsCountRef->{H};
 778     }
 779   }
 780 
 781   # Sort elements...
 782   for $AtomSymbol (sort {$a cmp $b} keys %{$ElementsCountRef}) {
 783     $MolecularFormula .= $AtomSymbol . ($ElementsCountRef->{$AtomSymbol} > 1 ? $ElementsCountRef->{$AtomSymbol} : '');
 784   }
 785   if (!$IncludeNonElements) {
 786     return $MolecularFormula;
 787   }
 788 
 789   # Sort non-elements...
 790   for $AtomSymbol (sort {$a cmp $b} keys %{$NonElementsCountRef}) {
 791     $MolecularFormula .= $AtomSymbol . ($NonElementsCountRef->{$AtomSymbol} > 1 ? $NonElementsCountRef->{$AtomSymbol} : '');
 792   }
 793 
 794   # Check formal charge...
 795   my($FormalCharge);
 796   $FormalCharge = $This->GetFormalCharge();
 797   if ($FormalCharge) {
 798     # Setup formal charge string...
 799     my($FormalChargeString);
 800     if ($FormalCharge == 1 ) {
 801       $FormalChargeString =  "+";
 802     }
 803     elsif ($FormalCharge == -1 ) {
 804       $FormalChargeString =  "-";
 805     }
 806     else {
 807       $FormalChargeString = ($FormalCharge > 0) ? ("+" . abs($FormalCharge)) : ("-" . abs($FormalCharge));
 808     }
 809     $MolecularFormula = "${MolecularFormula}${FormalChargeString}";
 810   }
 811 
 812   return $MolecularFormula;
 813 }
 814 
 815 # Count elements and non-elements in molecule and return references to hashes
 816 # containing count of elements and non-elements respectively. By default, missing
 817 # hydrogens are not added to the element hash.
 818 #
 819 #
 820 sub GetElementsAndNonElements {
 821   my($This, $IncludeMissingHydrogens) = @_;
 822   my($Atom, $AtomicNumber, $AtomSymbol, $NumOfMissingHydrogens, @Atoms, %ElementsCount, %NonElementsCount);
 823 
 824   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0;
 825 
 826   %ElementsCount = (); %NonElementsCount = ();
 827   $NumOfMissingHydrogens = 0;
 828 
 829   # Count elements and non elements...
 830   @Atoms = $This->GetAtoms();
 831   for $Atom (@Atoms) {
 832     $AtomicNumber = $Atom->GetAtomicNumber();
 833     $AtomSymbol = $Atom->GetAtomSymbol();
 834     if ($AtomicNumber) {
 835       if (exists $ElementsCount{$AtomSymbol}) {
 836         $ElementsCount{$AtomSymbol} += 1;
 837       }
 838       else {
 839         $ElementsCount{$AtomSymbol} = 1;
 840       }
 841       if ($IncludeMissingHydrogens) {
 842         if (!$Atom->IsHydrogen()) {
 843           $NumOfMissingHydrogens += $Atom->GetNumOfMissingHydrogens();
 844         }
 845       }
 846     }
 847     else {
 848       if (exists $NonElementsCount{$AtomSymbol}) {
 849         $NonElementsCount{$AtomSymbol} += 1;
 850       }
 851       else {
 852         $NonElementsCount{$AtomSymbol} = 1;
 853       }
 854     }
 855   }
 856   if ($IncludeMissingHydrogens && $NumOfMissingHydrogens) {
 857     $AtomSymbol = 'H';
 858     if (exists $ElementsCount{$AtomSymbol}) {
 859       $ElementsCount{$AtomSymbol} += $NumOfMissingHydrogens;
 860     }
 861     else {
 862       $ElementsCount{$AtomSymbol} = $NumOfMissingHydrogens;
 863     }
 864   }
 865 
 866   return (\%ElementsCount, \%NonElementsCount);
 867 }
 868 
 869 # Get number of element and non-elements in molecule. By default, missing
 870 # hydrogens are not added to element count.
 871 #
 872 sub GetNumOfElementsAndNonElements {
 873   my($This, $IncludeMissingHydrogens) = @_;
 874   my($ElementCount, $NonElementCount, $Atom);
 875 
 876   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0;
 877 
 878   ($ElementCount, $NonElementCount) = (0, 0);
 879 
 880   ATOM: for $Atom ($This->GetAtoms()) {
 881     if (!$Atom->GetAtomicNumber()) {
 882       $NonElementCount++;
 883       next ATOM;
 884     }
 885     # Process element...
 886     $ElementCount++;
 887     if ($IncludeMissingHydrogens) {
 888       if (!$Atom->IsHydrogen()) {
 889         $ElementCount += $Atom->GetNumOfMissingHydrogens();
 890       }
 891     }
 892   }
 893 
 894   return ($ElementCount, $NonElementCount);
 895 }
 896 
 897 # Calculate elemental composition and return reference to arrays
 898 # containing elements and their percent composition.
 899 #
 900 # Caveats:
 901 #   . By default, missing hydrogens are included
 902 #   . Non elemnents are ignored
 903 #   . Mass number are ignored
 904 #
 905 sub GetElementalComposition {
 906   my($This, $IncludeMissingHydrogens) = @_;
 907   my($MolecularFormula, $IncludeNonElements, $ElementsCountRef, $NonElementsCountRef, $ElementsRef, $ElementsCompositionRef);
 908 
 909   $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1;
 910 
 911   $IncludeNonElements = 0;
 912   ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens);
 913 
 914   $MolecularFormula = $This->GetMolecularFormula($IncludeMissingHydrogens, $IncludeNonElements);
 915 
 916   ($ElementsRef, $ElementsCompositionRef) = MolecularFormula::CalculateElementalComposition($MolecularFormula);
 917 
 918   return ($ElementsRef, $ElementsCompositionRef);
 919 }
 920 
 921 # Using refernece to element and its composition arrays, format composition information
 922 # as: Element: Composition;...
 923 #
 924 sub FormatElementalCompositionInformation {
 925   my($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter) = @_;
 926   my($This, $ElementsRef, $ElementCompositionRef, $Precision);
 927 
 928   if (_IsMolecule($FirstParameter)) {
 929     ($This, $ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter);
 930   }
 931   else {
 932     ($ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter);
 933   }
 934   my($FormattedInfo) = '';
 935 
 936   if (!(defined($ElementsRef) && @{$ElementsRef})) {
 937     carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements list is not defined or empty...";
 938     return undef;
 939   }
 940   if (!(defined($ElementCompositionRef) && @{$ElementCompositionRef})) {
 941     carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements composition list is not defined or empty...";
 942     return undef;
 943   }
 944 
 945   if (!defined $Precision) {
 946     $Precision = 2;
 947   }
 948 
 949   $FormattedInfo = MolecularFormula::FormatCompositionInfomation($ElementsRef, $ElementCompositionRef, $Precision);
 950 
 951   return $FormattedInfo;
 952 }
 953 
 954 # Copy molecule and its associated data using Storable::dclone and update:
 955 #
 956 #   o Atom references corresponding atoms and bonds objects
 957 #   o Bond object references
 958 #
 959 # Object IDs for Atoms and Bonds don't get changed. So there is no need to clear
 960 # up any exisiting ring data attached to molecule via graph as vertex IDs.
 961 #
 962 sub Copy {
 963   my($This) = @_;
 964   my($NewMolecule, $Atom, $NewAtom, $AtomID, @Atoms, @AtomIDs, %AtomsIDsToNewAtoms);
 965 
 966   $NewMolecule = Storable::dclone($This);
 967 
 968   # Update atom references stored as vertex property...
 969 
 970   @Atoms = (); @AtomIDs = ();
 971   %AtomsIDsToNewAtoms = ();
 972 
 973   @AtomIDs = $This->GetVertices();
 974   if (@AtomIDs) {
 975     @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs);
 976   }
 977 
 978   for $Atom (@Atoms) {
 979     $AtomID = $Atom->GetID();
 980 
 981     # Setup a reference to copied atom object...
 982     $NewAtom = $Atom->Copy();
 983     $AtomsIDsToNewAtoms{$AtomID} = $NewAtom;
 984 
 985     # Update atom reference to new atom object...
 986     $NewMolecule->UpdateVertexProperty('Atom', $NewAtom, $AtomID);
 987   }
 988 
 989   # Update bond object and bond atom references stored as edge property...
 990   my($Index, $AtomID1, $AtomID2, $Bond, $NewBond, $NewAtom1, $NewAtom2, @EdgesAtomsIDs);
 991   @EdgesAtomsIDs = ();
 992   @EdgesAtomsIDs = $This->GetEdges();
 993   for ($Index = 0; $Index < $#EdgesAtomsIDs; $Index += 2) {
 994     $AtomID1 = $EdgesAtomsIDs[$Index]; $AtomID2 = $EdgesAtomsIDs[$Index + 1];
 995 
 996     # Get reference to bond object...
 997     $Bond = $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2);
 998 
 999     # Make a new bond object and update its atom references...
1000     $NewBond = $Bond->Copy();
1001     $NewAtom1 = $AtomsIDsToNewAtoms{$AtomID1};
1002     $NewAtom2 = $AtomsIDsToNewAtoms{$AtomID2};
1003     $NewBond->SetAtoms($NewAtom1, $NewAtom2);
1004 
1005     # Update bond object reference in the new molecule...
1006     $NewMolecule->UpdateEdgeProperty('Bond', $NewBond, $AtomID1, $AtomID2);
1007   }
1008 
1009   return $NewMolecule;
1010 }
1011 
1012 # Get number of connected components...
1013 #
1014 sub GetNumOfConnectedComponents {
1015   my($This) = @_;
1016   my($NumOfComponents);
1017 
1018   $NumOfComponents = $This->GetConnectedComponentsVertices();
1019 
1020   return $NumOfComponents;
1021 }
1022 
1023 # Return a reference to an array containing molecules corresponding
1024 # to connected components sorted in decreasing order of component size...
1025 #
1026 sub GetConnectedComponents {
1027   my($This) = @_;
1028   my($Index, @ComponentMolecules, @ConnectedComponents);
1029 
1030   @ConnectedComponents = ();
1031   @ConnectedComponents = $This->GetConnectedComponentsVertices();
1032   @ComponentMolecules = ();
1033 
1034   for $Index (0 .. $#ConnectedComponents) {
1035     push @ComponentMolecules, $This->_GetConnectedComponent(\@ConnectedComponents, $Index);
1036   }
1037   return @ComponentMolecules;
1038 }
1039 
1040 # Return a reference to largest connected component as a molecule object...
1041 #
1042 sub GetLargestConnectedComponent {
1043   my($This) = @_;
1044   my($LargestComponentIndex, @ConnectedComponents);
1045 
1046   $LargestComponentIndex = 0;
1047   @ConnectedComponents = ();
1048   @ConnectedComponents = $This->GetConnectedComponentsVertices();
1049 
1050   return $This->_GetConnectedComponent(\@ConnectedComponents, $LargestComponentIndex);
1051 }
1052 
1053 # Return connected component as a molecule...
1054 #
1055 sub _GetConnectedComponent {
1056   my($This, $ConnectedComponentsRef, $ComponentIndex) = @_;
1057   my($ComponentMolecule);
1058 
1059   # Copy existing molecule...
1060   $ComponentMolecule = $This->Copy();
1061 
1062   # Delete all atoms besides the ones in specified component...
1063   $ComponentMolecule->_DeleteConnectedComponents($ConnectedComponentsRef, $ComponentIndex);
1064 
1065   # Clear any deteced rings...
1066   if ($ComponentMolecule->HasRings()) {
1067     $ComponentMolecule->ClearRings();
1068   }
1069   return $ComponentMolecule;
1070 }
1071 
1072 # Delete atoms corresponding to all connected components except the one specified...
1073 #
1074 sub _DeleteConnectedComponents {
1075   my($This, $ConnectedComponentsRef, $KeepComponentIndex) = @_;
1076   my($Index, $AtomID);
1077 
1078   INDEX: for $Index (0 .. $#{$ConnectedComponentsRef}) {
1079     if ($Index == $KeepComponentIndex) {
1080       next INDEX;
1081     }
1082     for $AtomID (@{$ConnectedComponentsRef->[$Index]}) {
1083       $This->DeleteVertex($AtomID);
1084     }
1085   }
1086   return $This;
1087 }
1088 
1089 # Return an array containing references to atom arrays corresponding to atoms of
1090 # connected components sorted in order of their decreasing size...
1091 #
1092 sub GetConnectedComponentsAtoms {
1093   my($This) = @_;
1094   my($Index, @ComponentsAtoms, @ConnectedComponents);
1095 
1096   @ConnectedComponents = ();
1097   @ConnectedComponents = $This->GetConnectedComponentsVertices();
1098 
1099   @ComponentsAtoms = ();
1100   for $Index (0 .. $#ConnectedComponents) {
1101     my(@ComponentAtoms);
1102 
1103     @ComponentAtoms = ();
1104     @ComponentAtoms = $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $Index);
1105     push @ComponentsAtoms, \@ComponentAtoms;
1106   }
1107   return @ComponentsAtoms;
1108 }
1109 
1110 # Return an array containing atoms correspondig to largest connected component...
1111 #
1112 sub GetLargestConnectedComponentAtoms {
1113   my($This) = @_;
1114   my($LargestComponentIndex, @ConnectedComponents);
1115 
1116   $LargestComponentIndex = 0;
1117   @ConnectedComponents = ();
1118   @ConnectedComponents = $This->GetConnectedComponentsVertices();
1119 
1120   return $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $LargestComponentIndex);
1121 }
1122 
1123 # Return an array containing atoms corresponding to specified connected component...
1124 #
1125 sub _GetConnectedComponentAtoms {
1126   my($This, $ConnectedComponentsRef, $ComponentIndex) = @_;
1127   my($AtomID, @AtomIDs, @ComponentAtoms);
1128 
1129   @ComponentAtoms = ();
1130   @AtomIDs = ();
1131 
1132   for $AtomID (@{$ConnectedComponentsRef->[$ComponentIndex]}) {
1133     push @AtomIDs, $AtomID;
1134   }
1135   @ComponentAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs);
1136 
1137   return @ComponentAtoms;
1138 }
1139 
1140 # Except for the largest connected component, delete atoms corresponding to all other
1141 # connected components...
1142 #
1143 sub KeepLargestComponent {
1144   my($This) = @_;
1145   my($LargestComponentIndex, @ConnectedComponents);
1146 
1147   @ConnectedComponents = ();
1148   @ConnectedComponents = $This->GetConnectedComponentsVertices();
1149   if (@ConnectedComponents == 1) {
1150     return $This;
1151   }
1152   $LargestComponentIndex = 0;
1153   $This->_DeleteConnectedComponents(\@ConnectedComponents, $LargestComponentIndex);
1154 
1155   # Clear any deteced rings...
1156   if ($This->HasRings()) {
1157     $This->ClearRings();
1158   }
1159 
1160   return $This;
1161 }
1162 
1163 # Get an array of topologically sorted atoms starting from a specified atom or
1164 # an arbitrary atom in the molecule...
1165 #
1166 sub GetTopologicallySortedAtoms {
1167   my($This, $StartAtom) = @_;
1168   my(@SortedAtoms);
1169 
1170   @SortedAtoms = ();
1171   if (defined($StartAtom) && !$This->HasAtom($StartAtom)) {
1172     carp "Warning: ${ClassName}->_GetTopologicallySortedAtoms: No atoms retrieved: Start atom doesn't exist...";
1173     return @SortedAtoms;
1174   }
1175   my($StartAtomID, @AtomIDs);
1176 
1177   @AtomIDs = ();
1178   $StartAtomID = defined($StartAtom) ? $StartAtom->GetID() : undef;
1179 
1180   @AtomIDs = $This->GetTopologicallySortedVertices($StartAtomID);
1181   @SortedAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs);
1182 
1183   return @SortedAtoms;
1184 }
1185 
1186 # Detect rings in molecule...
1187 #
1188 sub DetectRings {
1189   my($This) = @_;
1190 
1191   # Use graph method to detect all cycles and associate 'em to graph as graph
1192   # and vertex properties...
1193   return $This->DetectCycles();
1194 }
1195 
1196 # Clear rings in molecule...
1197 #
1198 sub ClearRings {
1199   my($This) = @_;
1200 
1201   # Use graph method to clear all cycles...
1202   $This->ClearCycles();
1203 
1204   return $This;
1205 }
1206 
1207 
1208 # Setup rings type paths to use during all ring related methods. Possible values:
1209 # Independent or All. Default is to use Independent rings.
1210 #
1211 sub SetActiveRings {
1212   my($This, $RingsType) = @_;
1213 
1214   if (!defined $This->SetActiveCyclicPaths($RingsType)) {
1215     return undef;
1216   }
1217   return $This;
1218 }
1219 
1220 # Identify aromatic rings in molecule and set aromaticity for corresponding atoms and bonds...
1221 #
1222 # Note:
1223 #   . Assumes ring detection has been performed.
1224 #   . Any exisiting atom and bond "Aromaticity" property is cleared up.
1225 #
1226 sub DetectAromaticity {
1227   my($This) = @_;
1228 
1229   # Delete aromaticity property for atoms and bonds...
1230   $This->DeleteAromaticity();
1231 
1232   # Any ring out there...
1233   if (!$This->HasRings()) {
1234     return $This;
1235   }
1236 
1237   if ($This->HasFusedRings()) {
1238     $This->_DetectAromaticityUsingFusedRingSets();
1239   }
1240   else {
1241     $This->_DetectAromaticityUsingIndividualRings();
1242   }
1243   return $This;
1244 }
1245 
1246 # Go over all rings and set aromaticity property for corresponding ring atoms
1247 # and bonds involved in aromatic rings...
1248 #
1249 sub _DetectAromaticityUsingIndividualRings {
1250   my($This) = @_;
1251 
1252   return $This->_DetectRingsAromaticity($This->GetRings());
1253 }
1254 
1255 # For each fused ring set, detect aromaticity by treating all of its ring as one aromatic
1256 # system for counting pi electrons to satisfy Huckel's rule; In case of a failure, rings in
1257 # fused set are treated individually for aromaticity detection. Additionally, non-fused
1258 # rings are handled on their own during aromaticity detection.
1259 #
1260 # Note:
1261 #   . pi electrons in common bonds involved in fused ring sets are only counted once.
1262 #
1263 #
1264 sub _DetectAromaticityUsingFusedRingSets {
1265   my($This) = @_;
1266   my($Index, $RingAtomsRef, $FusedRingSetRef, $FusedRingSetsRef, $NonFusedRingsRef, @FusedRingSetIsAromatic);
1267 
1268   ($FusedRingSetsRef, $NonFusedRingsRef) = $This->GetFusedAndNonFusedRings();
1269 
1270   @FusedRingSetIsAromatic = ();
1271   RINGSET: for $Index (0 .. $#{$FusedRingSetsRef}) {
1272     $FusedRingSetRef = $FusedRingSetsRef->[$Index];
1273     $FusedRingSetIsAromatic[$Index] = 0;
1274 
1275     my($NumOfPiElectronsInRingSet, $NumOfPiElectronsInRing, %FusedRingSetCommonBonds);
1276 
1277     $NumOfPiElectronsInRingSet = 0;
1278     %FusedRingSetCommonBonds = ();
1279 
1280     for $RingAtomsRef (@{$FusedRingSetRef}) {
1281       my(@RingBonds);
1282 
1283       @RingBonds = ();
1284       @RingBonds = $This->GetRingBonds(@{$RingAtomsRef});
1285       $NumOfPiElectronsInRing = $This->_GetNumOfPiElectrons($RingAtomsRef, \@RingBonds, \%FusedRingSetCommonBonds);
1286 
1287       if (!$NumOfPiElectronsInRing) {
1288         next RINGSET;
1289       }
1290       $NumOfPiElectronsInRingSet += $NumOfPiElectronsInRing;
1291     }
1292     if ($This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectronsInRingSet)) {
1293       $FusedRingSetIsAromatic[$Index] = 1;
1294     }
1295   }
1296 
1297   # Set atom and bond aromatic flags for ring sets whose pi electrons satisfy Huckel's rule; otherwise,
1298   # treat rings in a ring set as individual rings for detecting aromaticity...
1299   for $Index (0 .. $#{$FusedRingSetsRef}) {
1300     $FusedRingSetRef = $FusedRingSetsRef->[$Index];
1301     if ($FusedRingSetIsAromatic[$Index]) {
1302       $This->_SetRingsAromaticity(@{$FusedRingSetRef});
1303     }
1304     else {
1305       $This->_DetectRingsAromaticity(@{$FusedRingSetRef});
1306     }
1307   }
1308 
1309   $This->_DetectRingsAromaticity(@{$NonFusedRingsRef});
1310 
1311   return $This;
1312 }
1313 
1314 # Detect and set aromaticity for rings...
1315 #
1316 sub _DetectRingsAromaticity {
1317   my($This, @Rings) = @_;
1318   my($RingAtom, $RingBond, $RingAtomsRef);
1319 
1320   RING: for $RingAtomsRef (@Rings) {
1321     if (!$This->_CheckRingAromaticity(@{$RingAtomsRef})) {
1322       next RING;
1323     }
1324     $This->_SetRingAromaticity(@{$RingAtomsRef});
1325   }
1326   return $This;
1327 }
1328 
1329 # Set aromatic property for all all atoms and bonds involved in all specified rings..
1330 #
1331 sub _SetRingsAromaticity {
1332   my($This, @Rings) = @_;
1333   my($RingAtomsRef );
1334 
1335   for $RingAtomsRef (@Rings) {
1336     $This->_SetRingAromaticity(@{$RingAtomsRef});
1337   }
1338   return $This;
1339 }
1340 
1341 # Set aromatic property for all all atoms and bonds involved in ring..
1342 #
1343 sub _SetRingAromaticity {
1344   my($This, @RingAtoms) = @_;
1345   my($RingAtom, $RingBond);
1346 
1347   for $RingAtom (@RingAtoms) {
1348     $RingAtom->SetAromatic(1);
1349   }
1350   for $RingBond ($This->GetRingBonds(@RingAtoms)) {
1351     $RingBond->SetAromatic(1);
1352   }
1353   return $This;
1354 }
1355 
1356 
1357 # For a ring to be an aromatic ring, all of its atoms must have aromatic property
1358 # set.
1359 #
1360 sub IsRingAromatic {
1361   my($This, @RingAtoms) = @_;
1362   my($RingAtom);
1363 
1364   for $RingAtom (@RingAtoms) {
1365     if (!$RingAtom->IsAromatic()) {
1366       return 0;
1367     }
1368   }
1369   return 1;
1370 }
1371 
1372 # Delete aromatic property for all atoms and bonds...
1373 #
1374 sub DeleteAromaticity {
1375   my($This) = @_;
1376 
1377   return $This->_DeleteAtomsAndBondsAromaticity();
1378 }
1379 
1380 # Check ring aromaticity...
1381 #
1382 sub _CheckRingAromaticity {
1383   my($This, @RingAtoms) = @_;
1384   my($NumOfPiElectrons, @RingBonds);
1385 
1386   @RingBonds = ();
1387   @RingBonds = $This->GetRingBonds(@RingAtoms);
1388 
1389   $NumOfPiElectrons = $This->_GetNumOfPiElectrons(\@RingAtoms, \@RingBonds);
1390 
1391   return $This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectrons);
1392 }
1393 
1394 # Get number of pi electrons in the ring...
1395 #
1396 # Notes:
1397 #   . A ring containing 4n + 2 pi electrons, Huckel's rule, is considered aromatic.
1398 #
1399 #   . Hetrocyclic rings containing N, O and S atoms fall into two classes: Basic aromatic and
1400 #     Non-basic atomatic. In Basic aromatic hetrocyclic rings, hetroatom itself is involved
1401 #     in a double bond. (e.g. Pyridine) However, in non-basic hetrocyclic rings, hetroatom might have an attached
1402 #     hydrogen arom and the remaining lone pair contribute to electron delocalization and
1403 #     controbutes to 4n + 2 electrons. (e.g. Pyrrole, Furan)
1404 #
1405 #   . Only single or double bonds in the ring.
1406 #
1407 #   . Alternate single and double bonds with expception for lone pair contribution by hetro atoms.
1408 #
1409 #   . FusedBondsRef is used to avoid double counting of fused bonds.
1410 #
1411 sub _GetNumOfPiElectrons {
1412   my($This, $RingAtomsRef, $RingBondsRef, $FusedBondsVisitedRef) = @_;
1413   my($NumOfPiElectrons, $NumOfDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfHetroAtoms);
1414 
1415   ($NumOfPiElectrons, $NumOfDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfHetroAtoms) = (0) x 5;
1416 
1417   if (!(defined($RingAtomsRef) && @{$RingAtomsRef})) {
1418     return 0;
1419   }
1420 
1421   my($Index, $RingBond, $RingAtom, $RingBondID, $BondOrder, $PreviousIndex, $PreviousRingBond, $PreviousRingAtom, $CommonBondAtom, $NumOfRingBondsProcessed);
1422 
1423   $NumOfRingBondsProcessed = 0;
1424   RINGBOND: for $Index (0 .. $#{$RingBondsRef}) {
1425     $RingBond = $RingBondsRef->[$Index];
1426     $RingAtom = $RingAtomsRef->[$Index];
1427 
1428     # Is this ring bond part of a fused ring system which has been already processed?
1429     if (defined($FusedBondsVisitedRef) && $RingBond->GetNumOfRings() == 2) {
1430       $RingBondID = $RingBond->GetID();
1431       if (exists $FusedBondsVisitedRef->{$RingBondID}) {
1432         next RINGBOND;
1433       }
1434       $FusedBondsVisitedRef->{$RingBondID} = $RingBondID;
1435     }
1436 
1437     $NumOfRingBondsProcessed++;
1438     $BondOrder = $RingBond->GetBondOrder();
1439 
1440     if (($BondOrder > 2) || ($RingAtom->GetNumOfBonds() + $RingAtom->GetNumOfMissingHydrogens()) > 3) {
1441       # It's not an sp2 atom...
1442       return 0;
1443     }
1444 
1445     # For first ring, previous ring bond corrresponds to last ring bond...
1446     $PreviousIndex = $Index ? ($Index -1) : $#{$RingBondsRef};
1447     $PreviousRingBond = $RingBondsRef->[$PreviousIndex];
1448     $PreviousRingAtom = $RingAtomsRef->[$PreviousIndex];
1449 
1450     if ($BondOrder == 2) {
1451       # To count double bond in pi electron count, make sure it's in an alternate single/double bond configuration...
1452       if ($PreviousRingBond->GetBondOrder() != 1) {
1453         return 0;
1454       }
1455       $NumOfDoubleBonds += 1;
1456     }
1457     elsif ($BondOrder == 1.5) {
1458       # To count explicity set aromatic bond in pi electron count, the previoud bond should also be an explicit aromatic bond...
1459       if ($PreviousRingBond->GetBondOrder() != 1.5) {
1460         return 0;
1461       }
1462       $NumOfExplicitAromaticBonds += 1;
1463     }
1464     elsif ($BondOrder == 1) {
1465       # Contribution from potential hetro atoms...
1466       if ($PreviousRingBond->GetBondOrder() != 1) {
1467         # A potential hetro atom is already involved in a pi bond...
1468         next RINGBOND;
1469       }
1470       $CommonBondAtom = $RingBond->GetCommonAtom($PreviousRingBond);
1471       if (defined $CommonBondAtom) {
1472         if ($CommonBondAtom->IsNitrogen() || $CommonBondAtom->IsOxygen() || $CommonBondAtom->IsSulfur() ) {
1473           # Hetro atom lone pair contributes to pi electrons count...
1474           if ($CommonBondAtom->GetValenceFreeElectrons() >= 2) {
1475             $NumOfHetroAtoms += 1;
1476           }
1477         }
1478       }
1479     }
1480   }
1481   $NumOfPiElectrons = 0;
1482   if ($NumOfExplicitAromaticBonds) {
1483     if (($NumOfExplicitAromaticBonds != $NumOfRingBondsProcessed) || $NumOfDoubleBonds || $NumOfHetroAtoms) {
1484       # For an explicit aromatic bonds, all ring bonds must be explicitly set to aromatic...
1485       return 0;
1486     }
1487     $NumOfPiElectrons = $NumOfExplicitAromaticBonds * 1.5;
1488   }
1489   elsif ($NumOfDoubleBonds > 0) {
1490     # Must have some some double bonds..
1491     $NumOfPiElectrons = $NumOfDoubleBonds * 2 + $NumOfHetroAtoms * 2;
1492   }
1493 
1494   return $NumOfPiElectrons;
1495 }
1496 
1497 # Do pi electrons satify huckel's rule: Number of pi electrons correspond to 4n + 2 where
1498 # n is a positive integer...
1499 #
1500 sub _DoPiElectronSatifyHuckelsRule {
1501   my($This, $NumOfPiElectrons) = @_;
1502 
1503   $NumOfPiElectrons = $NumOfPiElectrons - 2;
1504 
1505   return ($NumOfPiElectrons > 0) ? (($NumOfPiElectrons % 4) ? 0 : 1) : 0;
1506 }
1507 
1508 # Delete aromatic property for all atoms and bonds...
1509 #
1510 sub _DeleteAtomsAndBondsAromaticity {
1511   my($This) = @_;
1512   my($Atom, $Bond);
1513 
1514   for $Atom ($This->GetAtoms()) {
1515     $Atom->DeleteAromatic();
1516   }
1517   for $Bond ($This->GetBonds()) {
1518     $Bond->DeleteAromatic();
1519   }
1520   return $This;
1521 }
1522 
1523 # Does molecule contains aromatic rings?
1524 #
1525 sub HasAromaticRings {
1526   my($This) = @_;
1527 
1528   return $This->GetNumOfAromaticRings() ? 1 : 0;
1529 }
1530 
1531 # Does molecule contains rings?
1532 #
1533 sub HasRings {
1534   my($This) = @_;
1535 
1536   return $This->IsCyclic();
1537 }
1538 
1539 # Does molecule contains only one ring?
1540 #
1541 sub HasOnlyOneRing {
1542   my($This) = @_;
1543 
1544   return $This->IsUnicyclic();
1545 }
1546 
1547 # Does molecule contains any rings?
1548 #
1549 sub HasNoRings {
1550   my($This) = @_;
1551 
1552   return $This->IsAcyclic();
1553 }
1554 
1555 # Get size of smallest ring...
1556 #
1557 sub GetSizeOfSmallestRing {
1558   my($This) = @_;
1559 
1560   return $This->GetSizeOfSmallestCycle();
1561 }
1562 
1563 # Get size of largest ring...
1564 #
1565 sub GetSizeOfLargestRing {
1566   my($This) = @_;
1567 
1568   return $This->GetSizeOfLargestCycle();
1569 }
1570 
1571 # Get number of rings...
1572 #
1573 sub GetNumOfRings {
1574   my($This) = @_;
1575 
1576   return $This->GetNumOfCycles();
1577 }
1578 
1579 # Get number of aromatic rings...
1580 #
1581 sub GetNumOfAromaticRings {
1582   my($This) = @_;
1583   my($NumOfRings);
1584 
1585   $NumOfRings = scalar $This->GetAromaticRings();
1586 
1587   return $NumOfRings;
1588 }
1589 
1590 # Get num of rings with odd size...
1591 #
1592 sub GetNumOfRingsWithOddSize {
1593   my($This) = @_;
1594 
1595   return $This->GetNumOfCyclesWithOddSize();
1596 }
1597 
1598 # Get num of rings with even size...
1599 #
1600 sub GetNumOfRingsWithEvenSize {
1601   my($This) = @_;
1602 
1603   return $This->GetNumOfCyclesWithEvenSize();
1604 }
1605 
1606 # Get num of rings with specified size...
1607 #
1608 sub GetNumOfRingsWithSize {
1609   my($This, $RingSize) = @_;
1610 
1611   return $This->GetNumOfCyclesWithSize($RingSize);
1612 }
1613 
1614 # Get num of rings with size less than a specified size...
1615 #
1616 sub GetNumOfRingsWithSizeLessThan {
1617   my($This, $RingSize) = @_;
1618 
1619   return $This->GetNumOfCyclesWithSizeLessThan($RingSize);
1620 }
1621 
1622 # Get num of rings with size greater than a specified size...
1623 #
1624 sub GetNumOfRingsWithSizeGreaterThan {
1625   my($This, $RingSize) = @_;
1626 
1627   return $This->GetNumOfCyclesWithSizeGreaterThan($RingSize);
1628 }
1629 
1630 # Get largest ring as an array containing ring atoms...
1631 #
1632 sub GetLargestRing {
1633   my($This) = @_;
1634 
1635   return $This->_GetRing($This->GetLargestCycle());
1636 }
1637 
1638 # Get smallest ring as an array containing ring atoms...
1639 #
1640 sub GetSmallestRing {
1641   my($This) = @_;
1642 
1643   return $This->_GetRing($This->GetSmallestCycle());
1644 }
1645 
1646 # Get rings as an array containing references to arrays with ring atoms...
1647 #
1648 sub GetRings {
1649   my($This) = @_;
1650 
1651   return $This->_GetRings($This->GetCycles());
1652 }
1653 
1654 # Get aromatic rings as an array containing references to arrays with ring atoms...
1655 #
1656 sub GetAromaticRings {
1657   my($This) = @_;
1658 
1659   return $This->_GetAromaticRings($This->GetCycles());
1660 }
1661 
1662 # Get odd size rings as an array containing references to arrays with ring atoms...
1663 #
1664 sub GetRingsWithOddSize {
1665   my($This) = @_;
1666 
1667   return $This->_GetRings($This->GetCyclesWithOddSize());
1668 }
1669 
1670 # Get even size rings as an array containing references to arrays with ring atoms...
1671 #
1672 sub GetRingsWithEvenSize {
1673   my($This) = @_;
1674 
1675   return $This->_GetRings($This->GetCyclesWithEvenSize());
1676 }
1677 
1678 # Get rings with a specific size as an array containing references to arrays with ring atoms...
1679 #
1680 sub GetRingsWithSize {
1681   my($This, $RingSize) = @_;
1682 
1683   return $This->_GetRings($This->GetCyclesWithSize($RingSize));
1684 }
1685 
1686 # Get rings with size less than a specific size as an array containing references to arrays with ring atoms...
1687 #
1688 sub GetRingsWithSizeLessThan {
1689   my($This, $RingSize) = @_;
1690 
1691   return $This->_GetRings($This->GetCyclesWithSizeLessThan($RingSize));
1692 }
1693 
1694 # Get rings with size greater than a specific size as an array containing references to arrays with ring atoms...
1695 #
1696 sub GetRingsWithSizeGreaterThan {
1697   my($This, $RingSize) = @_;
1698 
1699   return $This->_GetRings($This->GetCyclesWithSizeGreaterThan($RingSize));
1700 }
1701 
1702 # Generate an array of bond objects for an array of ring atoms and return an array
1703 # of bond objects...
1704 #
1705 sub GetRingBonds {
1706   my($This, @RingAtoms) = @_;
1707   my(@Bonds);
1708 
1709   @Bonds = ();
1710   if (!@RingAtoms) {
1711     # Return an empty ring bonds list...
1712     return @Bonds;
1713   }
1714 
1715   my(@RingAtomIDs);
1716 
1717   @RingAtomIDs = ();
1718   @RingAtomIDs = $This->_GetAtomsIDsFromAtoms(@RingAtoms);
1719   if (!@RingAtomIDs) {
1720     carp "Warning: ${ClassName}->GetRingBonds: No ring bonds retrieved: Atom IDs couldn't be retrieved for specified atoms...";
1721     return @Bonds;
1722   }
1723 
1724   # Add start atom to the end to make it a cyclic path for ring: It's taken out during conversion
1725   # of cyclic path to a ring...
1726   push @RingAtomIDs, $RingAtomIDs[0];
1727 
1728   return $This->_GetPathBonds(@RingAtomIDs);
1729 }
1730 
1731 # Does molecule has any fused rings?
1732 #
1733 sub HasFusedRings {
1734   my($This) = @_;
1735 
1736   return $This->HasFusedCycles();
1737 }
1738 
1739 # Get references to array of fused ring sets and non-fused rings. Fused ring sets array reference
1740 # contains refernces to arrays of rings; Non-fused rings array reference contains references to
1741 # arrays of ring atoms...
1742 # rings.
1743 #
1744 sub GetFusedAndNonFusedRings {
1745   my($This) = @_;
1746   my($FusedCyclesSetsRef, $NonFusedCyclesRef, @FusedRingSets, @NonFusedRings);
1747 
1748   @FusedRingSets = (); @NonFusedRings = ();
1749   ($FusedCyclesSetsRef, $NonFusedCyclesRef) = $This->GetFusedAndNonFusedCycles();
1750   if (!(defined($FusedCyclesSetsRef) && defined($NonFusedCyclesRef))) {
1751     return (\@FusedRingSets, \@NonFusedRings);
1752   }
1753   my($FusedCyclesSetRef);
1754 
1755   for $FusedCyclesSetRef (@{$FusedCyclesSetsRef}) {
1756     my(@FusedRingSet);
1757     @FusedRingSet = ();
1758     @FusedRingSet = $This->_GetRings(@{$FusedCyclesSetRef});
1759     push @FusedRingSets, \@FusedRingSet;
1760   }
1761 
1762   @NonFusedRings = $This->_GetRings(@{$NonFusedCyclesRef});
1763 
1764   return (\@FusedRingSets, \@NonFusedRings);
1765 }
1766 
1767 # Get rings as an array containing references to arrays with ring atoms...
1768 #
1769 sub _GetRings {
1770   my($This, @CyclicPaths) = @_;
1771   my($CyclicPath, @Rings);
1772 
1773   @Rings = ();
1774   if (!@CyclicPaths) {
1775     return @Rings;
1776   }
1777   if (!@CyclicPaths) {
1778     # Return an empty ring list...
1779     return @Rings;
1780   }
1781 
1782   for $CyclicPath (@CyclicPaths) {
1783     my(@RingAtoms);
1784     @RingAtoms = ();
1785     push @RingAtoms, $This->_GetRing($CyclicPath);
1786 
1787     push @Rings, \@RingAtoms;
1788   }
1789   return @Rings;
1790 }
1791 
1792 # Get aromatic rings as an array containing references to arrays with ring atoms...
1793 #
1794 sub _GetAromaticRings {
1795   my($This, @CyclicPaths) = @_;
1796   my($RingAtomsRef, @Rings, @AromaticRings);
1797 
1798   @AromaticRings = ();
1799   @Rings = $This->_GetRings(@CyclicPaths);
1800 
1801   if (!@Rings) {
1802     return @AromaticRings;
1803   }
1804   RING: for $RingAtomsRef (@Rings) {
1805     if (!$This->IsRingAromatic(@{$RingAtomsRef})) {
1806       next RING;
1807     }
1808     my(@RingAtoms);
1809     @RingAtoms = ();
1810     push @RingAtoms, @{$RingAtomsRef};
1811 
1812     push @AromaticRings, \@RingAtoms;
1813   }
1814   return @AromaticRings;
1815 }
1816 
1817 # Map atom IDs in cyclic path to atoms and return a reference to an array containing ring atoms...
1818 #
1819 # Note:
1820 #   . Start and end vertex is same for cyclic paths. So end atom is removed before
1821 #     returning atoms array as ring atoms...
1822 #
1823 sub _GetRing {
1824   my($This, $CyclicPath) = @_;
1825   my(@RingAtoms);
1826 
1827   @RingAtoms = ();
1828   if (!defined $CyclicPath) {
1829     # Return an empty atoms list...
1830     return @RingAtoms;
1831   }
1832 
1833   @RingAtoms = $This->_GetPathAtoms($CyclicPath);
1834   if (@RingAtoms) {
1835     pop @RingAtoms;
1836   }
1837   return @RingAtoms;
1838 }
1839 
1840 # Map atom IDs to atoms and return a reference to an array containing these atoms...
1841 #
1842 sub _GetPathAtoms {
1843   my($This, $Path) = @_;
1844   my(@PathAtoms);
1845 
1846   @PathAtoms = ();
1847   if (!defined $Path) {
1848     carp "Warning: ${ClassName}->_GetPathAtoms: No path atoms retrieved: Path must be defined...";
1849     return @PathAtoms;
1850   }
1851   my(@AtomIDs);
1852 
1853   @AtomIDs = ();
1854   @AtomIDs = $Path->GetVertices();
1855 
1856   @PathAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs);
1857 
1858   return @PathAtoms;
1859 }
1860 
1861 # Get bonds for a path specified by atom IDs...
1862 #
1863 sub _GetPathBonds {
1864   my($This, @AtomIDs) = @_;
1865   my($Index, $AtomID1, $AtomID2, @Bonds, @EdgesAtomIDs);
1866 
1867   @Bonds = (); @EdgesAtomIDs = ();
1868 
1869   if (!@AtomIDs || @AtomIDs == 1) {
1870     return @Bonds;
1871   }
1872 
1873   # Setup edges...
1874   for $Index (0 .. ($#AtomIDs - 1) ) {
1875     $AtomID1 = $AtomIDs[$Index];
1876     $AtomID2 = $AtomIDs[$Index + 1];
1877     push @EdgesAtomIDs, ($AtomID1, $AtomID2);
1878   }
1879   @Bonds =  $This->GetEdgesProperty('Bond', @EdgesAtomIDs);
1880 
1881   return @Bonds;
1882 }
1883 
1884 # Map atom ID to an atom...
1885 #
1886 sub _GetAtomFromAtomID {
1887   my($This, $AtomID) = @_;
1888 
1889   return $This->GetVertexProperty('Atom', $AtomID);
1890 }
1891 
1892 # Map atom IDs to atoms and return an array containing these atoms...
1893 #
1894 sub _GetAtomsFromAtomIDs {
1895   my($This, @AtomIDs) = @_;
1896 
1897   return $This->GetVerticesProperty('Atom', @AtomIDs);
1898 }
1899 
1900 # Map atoms to atom IDs and return an array containing these atoms...
1901 #
1902 sub _GetAtomsIDsFromAtoms {
1903   my($This, @Atoms) = @_;
1904 
1905   return map { $_->GetID() } @Atoms;
1906 }
1907 
1908 # Is atom in a ring?
1909 #
1910 sub _IsAtomInRing {
1911   my($This, $Atom) = @_;
1912 
1913   return $This->IsCyclicVertex($Atom->GetID());
1914 }
1915 
1916 # Is atom not in a ring?
1917 #
1918 sub _IsAtomNotInRing {
1919   my($This, $Atom) = @_;
1920 
1921   return $This->IsAcyclicVertex($Atom->GetID());
1922 }
1923 
1924 # Is atom only in one ring?
1925 #
1926 sub _IsAtomInOnlyOneRing {
1927   my($This, $Atom) = @_;
1928 
1929   return $This->IsUnicyclicVertex($Atom->GetID());
1930 }
1931 
1932 # Is atom in a ring of specified size?
1933 #
1934 sub _IsAtomInRingOfSize {
1935   my($This, $Atom, $RingSize) = @_;
1936 
1937   return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize) ? 1 : 0;
1938 }
1939 
1940 # Get size of smallest ring containing specified atom...
1941 #
1942 sub _GetSizeOfSmallestAtomRing {
1943   my($This, $Atom) = @_;
1944 
1945   return $This->GetSizeOfSmallestVertexCycle($Atom->GetID());
1946 }
1947 
1948 # Get size of largest ring containing specified atom...
1949 #
1950 sub _GetSizeOfLargestAtomRing {
1951   my($This, $Atom) = @_;
1952 
1953   return $This->GetSizeOfLargestVertexCycle($Atom->GetID());
1954 }
1955 
1956 # Get number of  rings containing specified atom...
1957 #
1958 sub _GetNumOfAtomRings {
1959   my($This, $Atom) = @_;
1960 
1961   return $This->GetNumOfVertexCycles($Atom->GetID());
1962 }
1963 
1964 # Get number of  rings with odd size containing specified atom...
1965 #
1966 sub _GetNumOfAtomRingsWithOddSize {
1967   my($This, $Atom) = @_;
1968 
1969   return $This->GetNumOfVertexCyclesWithOddSize($Atom->GetID());
1970 }
1971 
1972 # Get number of  rings with even size containing specified atom...
1973 #
1974 sub _GetNumOfAtomRingsWithEvenSize {
1975   my($This, $Atom) = @_;
1976 
1977   return $This->GetNumOfVertexCyclesWithEvenSize($Atom->GetID());
1978 }
1979 
1980 # Get number of  rings with specified size containing specified atom...
1981 #
1982 sub _GetNumOfAtomRingsWithSize {
1983   my($This, $Atom, $RingSize) = @_;
1984 
1985   return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize);
1986 }
1987 
1988 # Get number of  rings with size less than specified containing specified atom...
1989 #
1990 sub _GetNumOfAtomRingsWithSizeLessThan {
1991   my($This, $Atom, $RingSize) = @_;
1992 
1993   return $This->GetNumOfVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize);
1994 }
1995 
1996 # Get number of  rings with size greater than specified containing specified atom...
1997 #
1998 sub _GetNumOfAtomRingsWithSizeGreaterThan {
1999   my($This, $Atom, $RingSize) = @_;
2000 
2001   return $This->GetNumOfVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize);
2002 }
2003 
2004 # Get smallest ring as an array containing ring atoms...
2005 #
2006 sub _GetSmallestAtomRing {
2007   my($This, $Atom) = @_;
2008 
2009   return $This->_GetRing($This->GetSmallestVertexCycle($Atom->GetID()));
2010 }
2011 
2012 # Get odd size rings an array of references to arrays containing ring atoms...
2013 #
2014 sub _GetLargestAtomRing {
2015   my($This, $Atom) = @_;
2016 
2017   return $This->_GetRing($This->GetLargestVertexCycle($Atom->GetID()));
2018 }
2019 
2020 # Get all rings an array of references to arrays containing ring atoms...
2021 #
2022 sub _GetAtomRings {
2023   my($This, $Atom) = @_;
2024 
2025   return $This->_GetRings($This->GetVertexCycles($Atom->GetID()));
2026 }
2027 
2028 # Get odd size rings an array of references to arrays containing ring atoms...
2029 #
2030 sub _GetAtomRingsWithOddSize {
2031   my($This, $Atom) = @_;
2032 
2033   return $This->_GetRings($This->GetVertexCyclesWithOddSize($Atom->GetID()));
2034 }
2035 
2036 # Get even size rings an array of references to arrays containing ring atoms...
2037 #
2038 sub _GetAtomRingsWithEvenSize {
2039   my($This, $Atom) = @_;
2040 
2041   return $This->_GetRings($This->GetVertexCyclesWithEvenSize($Atom->GetID()));
2042 }
2043 
2044 # Get rings with specified size  an array of references to arrays containing ring atoms...
2045 #
2046 sub _GetAtomRingsWithSize {
2047   my($This, $Atom, $RingSize) = @_;
2048 
2049   return $This->_GetRings($This->GetVertexCyclesWithSize($Atom->GetID(), $RingSize));
2050 }
2051 
2052 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
2053 #
2054 sub _GetAtomRingsWithSizeLessThan {
2055   my($This, $Atom, $RingSize) = @_;
2056 
2057   return $This->_GetRings($This->GetVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize));
2058 }
2059 
2060 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
2061 #
2062 sub _GetAtomRingsWithSizeGreaterThan {
2063   my($This, $Atom, $RingSize) = @_;
2064 
2065   return $This->_GetRings($This->GetVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize));
2066 }
2067 
2068 # Is bond in a ring?
2069 #
2070 sub _IsBondInRing {
2071   my($This, $Bond) = @_;
2072   my($Atom1, $Atom2);
2073 
2074   ($Atom1, $Atom2) = $Bond->GetAtoms();
2075 
2076   return $This->IsCyclicEdge($Atom1->GetID(), $Atom2->GetID());
2077 }
2078 
2079 # Is bond not in a ring?
2080 #
2081 sub _IsBondNotInRing {
2082   my($This, $Bond) = @_;
2083   my($Atom1, $Atom2);
2084 
2085   ($Atom1, $Atom2) = $Bond->GetAtoms();
2086 
2087   return $This->IsAcyclicEdge($Atom1->GetID(), $Atom2->GetID());
2088 }
2089 
2090 # Is bond only in one ring?
2091 #
2092 sub _IsBondInOnlyOneRing {
2093   my($This, $Bond) = @_;
2094   my($Atom1, $Atom2);
2095 
2096   ($Atom1, $Atom2) = $Bond->GetAtoms();
2097 
2098   return $This->IsUnicyclicEdge($Atom1->GetID(), $Atom2->GetID());
2099 }
2100 
2101 # Is bond in a ring of specified size?
2102 #
2103 sub _IsBondInRingOfSize {
2104   my($This, $Bond, $RingSize) = @_;
2105   my($Atom1, $Atom2);
2106 
2107   ($Atom1, $Atom2) = $Bond->GetAtoms();
2108 
2109   return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize) ? 1 : 0;
2110 }
2111 
2112 # Get size of smallest ring containing specified bond...
2113 #
2114 sub _GetSizeOfSmallestBondRing {
2115   my($This, $Bond) = @_;
2116   my($Atom1, $Atom2);
2117 
2118   ($Atom1, $Atom2) = $Bond->GetAtoms();
2119 
2120   return $This->GetSizeOfSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID());
2121 }
2122 
2123 # Get size of largest ring containing specified bond...
2124 #
2125 sub _GetSizeOfLargestBondRing {
2126   my($This, $Bond) = @_;
2127   my($Atom1, $Atom2);
2128 
2129   ($Atom1, $Atom2) = $Bond->GetAtoms();
2130 
2131   return $This->GetSizeOfLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID());
2132 }
2133 
2134 # Get number of  rings containing specified bond...
2135 #
2136 sub _GetNumOfBondRings {
2137   my($This, $Bond) = @_;
2138   my($Atom1, $Atom2);
2139 
2140   ($Atom1, $Atom2) = $Bond->GetAtoms();
2141 
2142   return $This->GetNumOfEdgeCycles($Atom1->GetID(), $Atom2->GetID());
2143 }
2144 
2145 # Get number of  rings with odd size containing specified bond...
2146 #
2147 sub _GetNumOfBondRingsWithOddSize {
2148   my($This, $Bond) = @_;
2149   my($Atom1, $Atom2);
2150 
2151   ($Atom1, $Atom2) = $Bond->GetAtoms();
2152 
2153   return $This->GetNumOfEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID());
2154 }
2155 
2156 # Get number of  rings with even size containing specified bond...
2157 #
2158 sub _GetNumOfBondRingsWithEvenSize {
2159   my($This, $Bond) = @_;
2160   my($Atom1, $Atom2);
2161 
2162   ($Atom1, $Atom2) = $Bond->GetAtoms();
2163 
2164   return $This->GetNumOfEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID());
2165 }
2166 
2167 # Get number of  rings with specified size containing specified bond...
2168 #
2169 sub _GetNumOfBondRingsWithSize {
2170   my($This, $Bond, $RingSize) = @_;
2171   my($Atom1, $Atom2);
2172 
2173   ($Atom1, $Atom2) = $Bond->GetAtoms();
2174 
2175   return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize);
2176 }
2177 
2178 # Get number of  rings with size less than specified containing specified bond...
2179 #
2180 sub _GetNumOfBondRingsWithSizeLessThan {
2181   my($This, $Bond, $RingSize) = @_;
2182   my($Atom1, $Atom2);
2183 
2184   ($Atom1, $Atom2) = $Bond->GetAtoms();
2185 
2186   return $This->GetNumOfEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize);
2187 }
2188 
2189 # Get number of  rings with size greater than specified containing specified bond...
2190 #
2191 sub _GetNumOfBondRingsWithSizeGreaterThan {
2192   my($This, $Bond, $RingSize) = @_;
2193   my($Atom1, $Atom2);
2194 
2195   ($Atom1, $Atom2) = $Bond->GetAtoms();
2196 
2197   return $This->GetNumOfEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize);
2198 }
2199 
2200 # Get smallest ring as an array containing ring atoms...
2201 #
2202 sub _GetSmallestBondRing {
2203   my($This, $Bond) = @_;
2204   my($Atom1, $Atom2);
2205 
2206   ($Atom1, $Atom2) = $Bond->GetAtoms();
2207 
2208   return $This->_GetRing($This->GetSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID()));
2209 }
2210 
2211 # Get odd size rings an array of references to arrays containing ring atoms...
2212 #
2213 sub _GetLargestBondRing {
2214   my($This, $Bond) = @_;
2215   my($Atom1, $Atom2);
2216 
2217   ($Atom1, $Atom2) = $Bond->GetAtoms();
2218 
2219   return $This->_GetRing($This->GetLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID()));
2220 }
2221 
2222 # Get all rings an array of references to arrays containing ring atoms...
2223 #
2224 sub _GetBondRings {
2225   my($This, $Bond) = @_;
2226   my($Atom1, $Atom2);
2227 
2228   ($Atom1, $Atom2) = $Bond->GetAtoms();
2229 
2230   return $This->_GetRings($This->GetEdgeCycles($Atom1->GetID(), $Atom2->GetID()));
2231 }
2232 
2233 # Get odd size rings an array of references to arrays containing ring atoms...
2234 #
2235 sub _GetBondRingsWithOddSize {
2236   my($This, $Bond) = @_;
2237   my($Atom1, $Atom2);
2238 
2239   ($Atom1, $Atom2) = $Bond->GetAtoms();
2240 
2241   return $This->_GetRings($This->GetEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID()));
2242 }
2243 
2244 # Get even size rings an array of references to arrays containing ring atoms...
2245 #
2246 sub _GetBondRingsWithEvenSize {
2247   my($This, $Bond) = @_;
2248   my($Atom1, $Atom2);
2249 
2250   ($Atom1, $Atom2) = $Bond->GetAtoms();
2251 
2252   return $This->_GetRings($This->GetEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID()));
2253 }
2254 
2255 # Get rings with specified size  an array of references to arrays containing ring atoms...
2256 #
2257 sub _GetBondRingsWithSize {
2258   my($This, $Bond, $RingSize) = @_;
2259   my($Atom1, $Atom2);
2260 
2261   ($Atom1, $Atom2) = $Bond->GetAtoms();
2262 
2263   return $This->_GetRings($This->GetEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize));
2264 }
2265 
2266 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
2267 #
2268 sub _GetBondRingsWithSizeLessThan {
2269   my($This, $Bond, $RingSize) = @_;
2270   my($Atom1, $Atom2);
2271 
2272   ($Atom1, $Atom2) = $Bond->GetAtoms();
2273 
2274   return $This->_GetRings($This->GetEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize));
2275 }
2276 
2277 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
2278 #
2279 sub _GetBondRingsWithSizeGreaterThan {
2280   my($This, $Bond, $RingSize) = @_;
2281   my($Atom1, $Atom2);
2282 
2283   ($Atom1, $Atom2) = $Bond->GetAtoms();
2284 
2285   return $This->_GetRings($This->GetEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize));
2286 }
2287 
2288 
2289 # Get atom paths starting from a specified atom as a reference to an array containing references
2290 # to arrays with path atoms.
2291 #
2292 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with length
2293 # upto a specified length and sharing of bonds in paths traversed. By default, rings are
2294 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2295 #
2296 # Note:
2297 #    . For molecule without any rings, this method returns the same set of atom paths
2298 #      as GetAtomPathsStartingAtWithLengthUpto method.
2299 #
2300 sub GetAllAtomPathsStartingAtWithLengthUpto {
2301   my($This, $StartAtom, $Length, $AllowCycles) = @_;
2302 
2303   return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles);
2304 }
2305 
2306 # Get atom paths starting from a specified atom as a reference to an array containing references
2307 # to arrays with path atoms.
2308 #
2309 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with
2310 # specified length and sharing of bonds in paths traversed. By default, rings are
2311 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2312 #
2313 # Note:
2314 #    . For molecule without any rings, this method returns the same set of atom paths
2315 #      as GetAtomPathsStartingAtWithLengthUpto method.
2316 #
2317 sub GetAllAtomPathsStartingAtWithLength {
2318   my($This, $StartAtom, $Length, $AllowCycles) = @_;
2319 
2320   return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLength', $StartAtom, $Length, $AllowCycles);
2321 }
2322 
2323 # Get atom paths starting from a specified atom as a reference to an array containing references
2324 # to arrays with path atoms.
2325 #
2326 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with all
2327 # possible lengths and sharing of bonds in paths traversed. By default, rings are
2328 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2329 #
2330 # Note:
2331 #    . For molecule without any rings, this method returns the same set of atom paths
2332 #      as GetAtomPathsStartingAt method.
2333 #
2334 sub GetAllAtomPathsStartingAt {
2335   my($This, $StartAtom, $AllowCycles) = @_;
2336 
2337   return $This->_GetAtomPathsStartingAt('AllAtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles);
2338 }
2339 
2340 # Get atom paths starting from a specified atom as a reference to an array containing references
2341 # to arrays with path atoms.
2342 #
2343 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with length
2344 # upto a specified length and no sharing of bonds in paths traversed. By default, rings are
2345 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2346 #
2347 sub GetAtomPathsStartingAtWithLengthUpto {
2348   my($This, $StartAtom, $Length, $AllowCycles) = @_;
2349 
2350   return $This->_GetAtomPathsStartingAt('AtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles);
2351 }
2352 
2353 # Get atom paths starting from a specified atom as a reference to an array containing references
2354 # to arrays with path atoms.
2355 #
2356 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with
2357 # specified length and no sharing of bonds in paths traversed. By default, rings are
2358 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2359 #
2360 sub GetAtomPathsStartingAtWithLength {
2361   my($This, $StartAtom, $Length, $AllowCycles) = @_;
2362 
2363   return $This->_GetAtomPathsStartingAt('AtomPathsWithLength', $StartAtom, $Length, $AllowCycles);
2364 }
2365 
2366 # Get atom paths starting from a specified atom as a reference to an array containing references
2367 # to arrays with path atoms.
2368 #
2369 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with all
2370 # possible lengths and no sharing of bonds in paths traversed. By default, rings are
2371 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2372 #
2373 #
2374 sub GetAtomPathsStartingAt {
2375   my($This, $StartAtom, $AllowCycles) = @_;
2376 
2377   return $This->_GetAtomPathsStartingAt('AtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles);
2378 }
2379 
2380 # Get atom paths as an array containing references to arrays with path atoms...
2381 #
2382 sub _GetAtomPathsStartingAt {
2383   my($This, $Mode, $StartAtom, $Length, $AllowCycles) = @_;
2384   my(@AtomPaths);
2385 
2386   @AtomPaths = ();
2387   if (!defined $StartAtom) {
2388     carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom is not defined...";
2389     return @AtomPaths;
2390   }
2391   if (!$This->HasAtom($StartAtom)) {
2392     carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom doesn't exist...";
2393     return @AtomPaths;
2394   }
2395   my($StartAtomID, @Paths);
2396 
2397   $StartAtomID = $StartAtom->GetID();
2398   @Paths = ();
2399 
2400   # Collect appropriate atom paths...
2401   MODE: {
2402     if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { @Paths = $This->GetPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; }
2403     if ($Mode =~ /^AtomPathsWithLength$/i) { @Paths = $This->GetPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; }
2404     if ($Mode =~ /^AtomPathsWithAllLengths$/i) { @Paths = $This->GetPathsStartingAt($StartAtomID, $AllowCycles); last MODE; }
2405 
2406     if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { @Paths = $This->GetAllPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; }
2407     if ($Mode =~ /^AllAtomPathsWithLength$/i) { @Paths = $This->GetAllPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; }
2408     if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { @Paths = $This->GetAllPathsStartingAt($StartAtomID, $AllowCycles); last MODE; }
2409 
2410     print "Warn: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Mode, $Mode, is not supported...";
2411     return @AtomPaths;
2412   }
2413   return $This->_GetAtomPathsFromPaths(\@Paths);
2414 }
2415 
2416 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2417 # path atoms.
2418 #
2419 # Path atoms correspond to to all possible paths for each atom in molecule with length
2420 # upto a specified length and sharing of bonds in paths traversed. By default, rings are
2421 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2422 #
2423 # Notes:
2424 #    . For molecule without any rings, this method returns the same set of atom paths
2425 #      as GetAtomPathsWithLengthUpto method.
2426 #
2427 sub GetAllAtomPathsWithLengthUpto {
2428   my($This, $Length, $AllowCycles) = @_;
2429 
2430   return $This->_GetAtomPaths('AllAtomPathsWithLengthUpto', $Length, $AllowCycles);
2431 }
2432 
2433 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2434 # path atoms.
2435 #
2436 # Path atoms correspond to to all possible paths for each atom in molecule with
2437 # a specified length and sharing of bonds in paths traversed. By default, rings are
2438 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2439 #
2440 # Notes:
2441 #    . For molecule without any rings, this method returns the same set of atom paths
2442 #      as GetAtomPathsWithLengthUpto method.
2443 #
2444 sub GetAllAtomPathsWithLength {
2445   my($This, $Length, $AllowCycles) = @_;
2446 
2447   return $This->_GetAtomPaths('AllAtomPathsWithLength', $Length, $AllowCycles);
2448 }
2449 
2450 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2451 # path atoms.
2452 #
2453 # Path atoms correspond to to all possible paths for each atom in molecule with all
2454 # possible lengths and sharing of bonds in paths traversed. By default, rings are
2455 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2456 #
2457 # Notes:
2458 #    . For molecule without any rings, this method returns the same set of atom paths
2459 #      as GetAtomPaths method.
2460 #
2461 sub GetAllAtomPaths {
2462   my($This, $AllowCycles) = @_;
2463 
2464   return $This->_GetAtomPaths('AllAtomPathsWithAllLengths', undef, $AllowCycles);
2465 }
2466 
2467 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2468 # path atoms.
2469 #
2470 # Path atoms correspond to to all possible paths for each atom in molecule with length
2471 # upto a specified length and no sharing of bonds in paths traversed. By default, rings are
2472 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2473 #
2474 sub GetAtomPathsWithLengthUpto {
2475   my($This, $Length, $AllowCycles) = @_;
2476 
2477   return $This->_GetAtomPaths('AtomPathsWithLengthUpto', $Length, $AllowCycles);
2478 }
2479 
2480 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2481 # path atoms.
2482 #
2483 # Path atoms correspond to to all possible paths for each atom in molecule with
2484 # a specified length and no sharing of bonds in paths traversed. By default, rings are
2485 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2486 #
2487 sub GetAtomPathsWithLength {
2488   my($This, $Length, $AllowCycles) = @_;
2489 
2490   return $This->_GetAtomPaths('AtomPathsWithLength', $Length, $AllowCycles);
2491 }
2492 
2493 
2494 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2495 # path atoms.
2496 #
2497 # Path atoms correspond to to all possible paths for each atom in molecule with all
2498 # possible lengths and no sharing of bonds in paths traversed. By default, rings are
2499 # included in paths. A path containing a ring is terminated at an atom completing the ring.
2500 #
2501 sub GetAtomPaths {
2502   my($This, $AllowCycles) = @_;
2503 
2504   return $This->_GetAtomPaths('AtomPathsWithAllLengths', undef, $AllowCycles);
2505 }
2506 
2507 # Get atom paths for all atoms as a reference to an array containing references to arrays with
2508 # path atoms.
2509 #
2510 sub _GetAtomPaths {
2511   my($This, $Mode, $Length, $AllowCycles) = @_;
2512   my($PathsRef, @AtomPaths);
2513 
2514   @AtomPaths = ();
2515   # Collect appropriate atom paths...
2516   MODE: {
2517     if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetPathsWithLengthUpto($Length, $AllowCycles); last MODE; }
2518     if ($Mode =~ /^AtomPathsWithLength$/i) { $PathsRef = $This->GetPathsWithLength($Length, $AllowCycles); last MODE; }
2519     if ($Mode =~ /^AtomPathsWithAllLengths$/i) { $PathsRef = $This->GetPaths($AllowCycles); last MODE; }
2520 
2521     if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetAllPathsWithLengthUpto($Length, $AllowCycles); last MODE; }
2522     if ($Mode =~ /^AllAtomPathsWithLength$/i) { $PathsRef = $This->GetAllPathsWithLength($Length, $AllowCycles); last MODE; }
2523     if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { $PathsRef = $This->GetAllPaths($AllowCycles); last MODE; }
2524 
2525     print "Warn: ${ClassName}->_GetAtomPaths: No atom paths retrieved: Mode, $Mode, is not supported...";
2526     return \@AtomPaths;
2527   }
2528   return $This->_GetAtomPathsFromPaths($PathsRef);
2529 }
2530 
2531 # Get atom paths as an array reference containing references to arrays with path atoms...
2532 #
2533 sub _GetAtomPathsFromPaths {
2534   my($This, $PathsRef) = @_;
2535   my($Path, @AtomPaths);
2536 
2537   @AtomPaths = ();
2538   if (!defined $PathsRef) {
2539     return \@AtomPaths;
2540   }
2541   if (!@{$PathsRef}) {
2542     # Return an empty atom paths list...
2543     return \@AtomPaths;
2544   }
2545   for $Path (@{$PathsRef}) {
2546     my(@PathAtoms);
2547     @PathAtoms = ();
2548     @PathAtoms = $This->_GetAtomPathFromPath($Path);
2549 
2550     push @AtomPaths, \@PathAtoms;
2551   }
2552   return \@AtomPaths;
2553 }
2554 
2555 # Generate an array of bond objects for an array of path atoms and return an array
2556 # of bond objects...
2557 #
2558 sub GetAtomPathBonds {
2559   my($This, @PathAtoms) = @_;
2560   my(@Bonds);
2561 
2562   if (!@PathAtoms) {
2563     # Return an empty ring bonds list...
2564     return @Bonds;
2565   }
2566   my(@PathAtomIDs);
2567 
2568   @PathAtomIDs = ();
2569   @PathAtomIDs = $This->_GetAtomsIDsFromAtoms(@PathAtoms);
2570 
2571   return $This->_GetPathBonds(@PathAtomIDs);
2572 }
2573 
2574 # Map atom IDs in path to atoms and return a reference to an array containing ring atoms...
2575 #
2576 sub _GetAtomPathFromPath {
2577   my($This, $Path) = @_;
2578   my(@PathAtoms);
2579 
2580   @PathAtoms = ();
2581   if (!defined $Path) {
2582     # Return an empty atoms list...
2583     return @PathAtoms;
2584   }
2585 
2586   return $This->_GetPathAtoms($Path);
2587 }
2588 
2589 # Get atom paths between two specified atoms as a reference to an array containing references
2590 # to arrays with path atoms. For molecules with rings, atom paths array contains may contain
2591 # two paths.
2592 #
2593 sub GetAtomPathsBetween {
2594   my($This, $StartAtom, $EndAtom) = @_;
2595   my(@AtomPaths);
2596 
2597   @AtomPaths = ();
2598   if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) {
2599     carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: Start atom is not defined  or it doesn't exist...";
2600     return @AtomPaths;
2601   }
2602   if (!(defined($EndAtom) && $This->HasAtom($EndAtom))) {
2603     carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: End atom is not defined  or it doesn't exist...";
2604     return @AtomPaths;
2605   }
2606   return $This->_GetAtomPathsBetween($StartAtom, $EndAtom);
2607 }
2608 
2609 # Get atom paths between two specified atoms as a reference to an array containing references
2610 # to arrays with path atoms.
2611 #
2612 sub _GetAtomPathsBetween {
2613   my($This, $StartAtom, $EndAtom) = @_;
2614   my($StartAtomID, $EndAtomID, @Paths);
2615 
2616   $StartAtomID = $StartAtom->GetID();
2617   $EndAtomID = $EndAtom->GetID();
2618 
2619   @Paths = ();
2620   @Paths = $This->GetPathsBetween($StartAtomID, $EndAtomID);
2621 
2622   return $This->_GetAtomPathsFromPaths(\@Paths);
2623 }
2624 
2625 # Get atom neighborhoods around a specified atom as an array containing references
2626 # to arrays with neighborhood atoms at different radii upto specified radius...
2627 #
2628 sub GetAtomNeighborhoodsWithRadiusUpto {
2629   my($This, $StartAtom, $Radius) = @_;
2630 
2631   return $This->_GetAtomNeighborhoods('RadiusUpto', $StartAtom, $Radius);
2632 }
2633 
2634 # Get atom neighborhoods around a specified atom as an array containing references
2635 # to arrays with neighborhood atoms at possible radii...
2636 #
2637 sub GetAtomNeighborhoods {
2638   my($This, $StartAtom) = @_;
2639 
2640   return $This->_GetAtomNeighborhoods('AllRadii', $StartAtom, undef);
2641 }
2642 
2643 # Get atom neighborhood around a specified atom, along with their successor connected atoms, collected
2644 # with in a specified radius as a list containing references to lists with first value corresponding to neighborhood
2645 # atom at a specific radius and second value as reference to a list containing its successor connected atoms.
2646 #
2647 # For a neighborhood atom at each radius level, the successor connected atoms correspond to the
2648 # neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last
2649 # radius level don't contain any successor atoms which fall outside the range of specified radius.
2650 #
2651 sub GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto {
2652   my($This, $StartAtom, $Radius) = @_;
2653 
2654   return $This->_GetAtomNeighborhoods('WithSuccessorsAndRadiusUpto', $StartAtom, $Radius);
2655 }
2656 
2657 # Get atom neighborhood around a specified atom, along with their successor connected atoms, collected
2658 # at all radii as a list containing references to lists with first value corresponding to neighborhood
2659 # atom at a specific radius and second value as reference to a list containing its successor connected atoms.
2660 #
2661 # For a neighborhood atom at each radius level, the successor connected atoms correspond to the
2662 # neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last
2663 # radius level don't contain any successor atoms which fall outside the range of specified radius.
2664 #
2665 #
2666 sub GetAtomNeighborhoodsWithSuccessorAtoms {
2667   my($This, $StartAtom) = @_;
2668 
2669   return $This->_GetAtomNeighborhoods('WithSuccessorsAndAllRadii', $StartAtom, undef);
2670 }
2671 
2672 # Get atom neighborhoods...
2673 #
2674 sub _GetAtomNeighborhoods {
2675   my($This, $Mode, $StartAtom, $Radius) = @_;
2676   my(@AtomNeighborhoods);
2677 
2678   @AtomNeighborhoods = ();
2679 
2680   if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) {
2681     carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Start atom is not defined  or it doesn't exist...";
2682     return @AtomNeighborhoods;
2683   }
2684   if ($Mode =~ /^(RadiusUpto|WithSuccessorsAndRadiusUpto)$/i) {
2685     if (!(defined($Radius) && $Radius > 0)) {
2686       carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Radius is not defined or it's <= 0 ...";
2687       return @AtomNeighborhoods;
2688     }
2689   }
2690 
2691   # Collect neighborhood atom IDs...
2692   my($StartAtomID, @NeighborhoodAtomIDs, @NeighborhoodAtomIDsWithSuccessors);
2693 
2694   @NeighborhoodAtomIDs = (); @NeighborhoodAtomIDsWithSuccessors = ();
2695   $StartAtomID = $StartAtom->GetID();
2696 
2697   MODE: {
2698     if ($Mode =~ /^RadiusUpto$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVerticesWithRadiusUpto($StartAtomID, $Radius); last MODE; }
2699     if ($Mode =~ /^AllRadii$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVertices($StartAtomID); last MODE; }
2700 
2701     if ($Mode =~ /^WithSuccessorsAndRadiusUpto$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessorsAndRadiusUpto($StartAtomID, $Radius); last MODE; }
2702     if ($Mode =~ /^WithSuccessorsAndAllRadii$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessors($StartAtomID); last MODE; }
2703 
2704     print "Warn: ${ClassName}->_GetAtomNeighborhood: No atom neighborhoods retrieved: Mode, $Mode, is not supported...";
2705     return @AtomNeighborhoods;
2706   }
2707   if ($Mode =~ /^(RadiusUpto|AllRadii)$/i) {
2708     return $This->_GetNeighborhoodAtomsFromAtomIDs(\@NeighborhoodAtomIDs);
2709   }
2710   elsif ($Mode =~ /^(WithSuccessorsAndRadiusUpto|WithSuccessorsAndAllRadii)$/i) {
2711     return $This->_GetNeighborhoodAtomsWithSuccessorsFromAtomIDs(\@NeighborhoodAtomIDsWithSuccessors);
2712   }
2713 
2714   return @AtomNeighborhoods;
2715 }
2716 
2717 # Map neighborhood atom IDs to atoms...
2718 #
2719 sub _GetNeighborhoodAtomsFromAtomIDs {
2720   my($This, $NeighborhoodsAtomIDsRef) = @_;
2721   my($NeighborhoodAtomIDsRef, @AtomNeighborhoods);
2722 
2723   @AtomNeighborhoods = ();
2724   for $NeighborhoodAtomIDsRef (@{$NeighborhoodsAtomIDsRef}) {
2725     my(@AtomNeighborhood);
2726 
2727     @AtomNeighborhood = ();
2728     @AtomNeighborhood = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomIDsRef});
2729     push @AtomNeighborhoods, \@AtomNeighborhood;
2730   }
2731   return @AtomNeighborhoods;
2732 }
2733 
2734 # Map neighborhood atom IDs with successors to atoms...
2735 #
2736 sub _GetNeighborhoodAtomsWithSuccessorsFromAtomIDs {
2737   my($This, $NeighborhoodsAtomIDsWithSuccessorsRef) = @_;
2738   my($Depth, $NeighborhoodAtomIDsWithSuccessorsRef, $NeighborhoodAtomIDWithSuccessorsRef, $NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef, @AtomNeighborhoods);
2739 
2740   $Depth = 0;
2741   @AtomNeighborhoods = ();
2742 
2743   # Go over neighborhoods at each level...
2744   for $NeighborhoodAtomIDsWithSuccessorsRef (@{$NeighborhoodsAtomIDsWithSuccessorsRef}) {
2745     @{$AtomNeighborhoods[$Depth]} = ();
2746 
2747     # Go over the neighborhood atoms and their successors at a specific level..
2748     for $NeighborhoodAtomIDWithSuccessorsRef (@{$NeighborhoodAtomIDsWithSuccessorsRef}) {
2749       my($NeighborhoodAtom, @NeighborhoodAtomWithSuccessors, @NeighborhoodAtomSuccessorAtoms);
2750 
2751       @NeighborhoodAtomWithSuccessors = (); @NeighborhoodAtomSuccessorAtoms = ();
2752       ($NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef) = @{$NeighborhoodAtomIDWithSuccessorsRef};
2753 
2754       # Map atom IDs to atoms...
2755       $NeighborhoodAtom = $This->_GetAtomFromAtomID($NeighborhoodAtomID);
2756       if (@{$NeighborhoodAtomSuccessorsIDsRef}) {
2757         @NeighborhoodAtomSuccessorAtoms = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomSuccessorsIDsRef});
2758       }
2759 
2760       # Store an atom and its successors at each level in an array...
2761       push @NeighborhoodAtomWithSuccessors, ($NeighborhoodAtom, \@NeighborhoodAtomSuccessorAtoms);
2762 
2763       push @{$AtomNeighborhoods[$Depth]} , \@NeighborhoodAtomWithSuccessors;
2764     }
2765       $Depth++;
2766   }
2767   return @AtomNeighborhoods;
2768 }
2769 
2770 # Get next object ID...
2771 sub _GetNewObjectID {
2772   $ObjectID++;
2773   return $ObjectID;
2774 }
2775 
2776 # Is aromatic property set for the molecule?
2777 sub IsAromatic {
2778   my($This) = @_;
2779   my($Aromatic);
2780 
2781   $Aromatic = $This->GetAromatic();
2782 
2783   return (defined($Aromatic) && $Aromatic) ? 1 : 0;
2784 }
2785 
2786 # Does molecule contains any atoms with non-zero Z coordiates?
2787 sub IsThreeDimensional {
2788   my($This) = @_;
2789   my($Atom, @Atoms);
2790 
2791   @Atoms = $This->GetAtoms();
2792   ATOM: for $Atom (@Atoms) {
2793       if ($Atom->GetZ() != 0) {
2794         return 1;
2795       }
2796   }
2797   return 0;
2798 }
2799 
2800 
2801 # Is it a molecule object?
2802 sub IsMolecule ($) {
2803   my($Object) = @_;
2804 
2805   return _IsMolecule($Object);
2806 }
2807 
2808 # Return a string containing vertices, edges and other properties...
2809 sub StringifyMolecule {
2810   my($This) = @_;
2811   my($MoleculeString, $ID, $Name, $NumOfAtoms, $NumOfBonds, $MolecularFormula, $NumOfRings, $MolecularWeight, $ExactMass, $FormalCharge, $SpinMultiplicity, $Charge, $ElementsRef, $ElementsCompositionRef, $ElementalComposition);
2812 
2813   $ID = $This->GetID();
2814   $Name = $This->GetName();
2815   $NumOfAtoms = $This->GetNumOfAtoms();
2816   $NumOfBonds = $This->GetNumOfBonds();
2817 
2818   $NumOfRings = $This->GetNumOfRings();
2819   if (!defined $NumOfRings) {
2820     $NumOfRings = 'undefined';
2821   }
2822 
2823   $MolecularFormula = $This->GetMolecularFormula();
2824 
2825   $MolecularWeight = $This->GetMolecularWeight();
2826   $MolecularWeight = round($MolecularWeight, 4) + 0;
2827 
2828   $ExactMass = $This->GetExactMass();
2829   $ExactMass = round($ExactMass, 4) + 0;
2830 
2831   $FormalCharge = $This->GetFormalCharge();
2832   $Charge = $This->GetCharge();
2833 
2834   $SpinMultiplicity = $This->GetSpinMultiplicity();
2835 
2836   ($ElementsRef, $ElementsCompositionRef) = $This->GetElementalComposition();
2837   $ElementalComposition = 'None';
2838   if (defined($ElementsRef) && @{$ElementsRef}) {
2839     $ElementalComposition = "[ " . FormatElementalCompositionInformation($ElementsRef, $ElementsCompositionRef) . " ]";
2840   }
2841 
2842   $MoleculeString = "Molecule: ID: $ID; Name: \"$Name\"; NumOfAtoms: $NumOfAtoms; NumOfBonds: $NumOfBonds; NumOfRings: $NumOfRings; MolecularFormula: $MolecularFormula; MolecularWeight: $MolecularWeight; ExactMass: $ExactMass; FormalCharge: $FormalCharge; Charge: $Charge; SpinMultiplicity: $SpinMultiplicity; ElementalComposition: $ElementalComposition";
2843 
2844   return $MoleculeString;
2845 }
2846 
2847 # Is it a molecule object?
2848 sub _IsMolecule {
2849   my($Object) = @_;
2850 
2851   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
2852 }
2853