Mercurial > repos > deepakjadmin > mayatool3_test2
diff lib/Molecule.pm @ 0:4816e4a8ae95 draft default tip
Uploaded
author | deepakjadmin |
---|---|
date | Wed, 20 Jan 2016 09:23:18 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/Molecule.pm Wed Jan 20 09:23:18 2016 -0500 @@ -0,0 +1,5796 @@ +package Molecule; +# +# $RCSfile: Molecule.pm,v $ +# $Date: 2015/02/28 20:47:18 $ +# $Revision: 1.59 $ +# +# Author: Manish Sud <msud@san.rr.com> +# +# Copyright (C) 2015 Manish Sud. All rights reserved. +# +# This file is part of MayaChemTools. +# +# MayaChemTools is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 3 of the License, or (at your option) any +# later version. +# +# MayaChemTools is distributed in the hope that it will be useful, but without +# any warranty; without even the implied warranty of merchantability of fitness +# for a particular purpose. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or +# write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, +# Boston, MA, 02111-1307, USA. +# + +use strict; +use Carp; +use Exporter; +use Storable (); +use Scalar::Util (); +use ObjectProperty; +use MathUtil; +use PeriodicTable; +use Text::ParseWords; +use TextUtil; +use FileUtil; +use Graph; +use Atom; +use Bond; +use MolecularFormula; + +use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + +@ISA = qw(Graph ObjectProperty Exporter); +@EXPORT = qw(IsMolecule); +@EXPORT_OK = qw(FormatElementalCompositionInformation GetSupportedAromaticityModels IsSupportedAromaticityModel); + +%EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); + +# Setup class variables... +my($ClassName, $ObjectID, %AromaticityModelsDataMap, %CanonicalAromaticityModelNamesMap); +_InitializeClass(); + +# Overload Perl functions... +use overload '""' => 'StringifyMolecule'; + +# Class constructor... +sub new { + my($Class, %NamesAndValues) = @_; + + # Initialize object... + my $This = $Class->SUPER::new(); + bless $This, ref($Class) || $Class; + $This->_InitializeMolecule(); + + if (keys %NamesAndValues) { $This->_InitializeMoleculeProperties(%NamesAndValues); } + + return $This; +} + +# Initialize object data... +sub _InitializeMolecule { + my($This) = @_; + my($ObjectID) = _GetNewObjectID(); + + # All other property names and values along with all Set/Get<PropertyName> methods + # are implemented on-demand using ObjectProperty class. + $This->{ID} = $ObjectID; + $This->{Name} = "Molecule ${ObjectID}"; +} + +# Initialize molecule properties... +sub _InitializeMoleculeProperties { + my($This, %NamesAndValues) = @_; + + my($Name, $Value, $MethodName); + while (($Name, $Value) = each %NamesAndValues) { + $MethodName = "Set${Name}"; + $This->$MethodName($Value); + } +} + +# Initialize class ... +sub _InitializeClass { + #Class name... + $ClassName = __PACKAGE__; + + # ID to keep track of objects... + $ObjectID = 0; + + # Load molecule class data... + _LoadMoleculeClassData(); +} + +# Setup an explicit SetID method to block setting of ID by AUTOLOAD function... +sub SetID { + my($This, $Value) = @_; + + carp "Warning: ${ClassName}->SetID: Object ID can't be changed: it's used for internal tracking..."; + + return $This; +} + +# Add an atom... +sub AddAtom { + my($This, $Atom) = @_; + + if (!defined $Atom) { + carp "Warning: ${ClassName}->AddAtom: No atom added: Atom must be specified..."; + return undef; + } + if ($This->HasAtom($Atom)) { + carp "Warning: ${ClassName}->AddAtom: No atom added: Atom already exists..."; + return undef; + } + return $This->_AddAtom($Atom); +} + +# Add an atom... +sub _AddAtom { + my($This, $Atom) = @_; + + # Assign atom to this molecule... + $Atom->_SetMolecule($This); + + # Add it to the graph as a vertex... + my($AtomID); + $AtomID = $Atom->GetID(); + $This->AddVertex($AtomID); + $This->SetVertexProperty('Atom', $Atom, $AtomID); + + return $This; +} + +# Add atoms... +sub AddAtoms { + my($This, @Atoms) = @_; + + if (!@Atoms) { + carp "Warning: ${ClassName}->AddAtoms: No atoms added: Atoms list must be specified..."; + return undef; + } + my($Atom); + for $Atom (@Atoms) { + $This->AddAtom($Atom); + } + return $This; +} + +# Create an atom and add it to molecule... +sub NewAtom { + my($This, %NamesAndValues) = @_; + my($Atom); + + $Atom = new Atom(%NamesAndValues); + $This->AddAtom($Atom); + + return $Atom; +} + +# Delete an atom... +sub DeleteAtom { + my($This, $Atom) = @_; + + if (!defined $Atom) { + carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom must be specified..."; + return undef; + } + # Does the atom exist in molecule? + if (!$This->HasAtom($Atom)) { + carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom doesn't exist..."; + return undef; + } + return $This->_DeleteAtom($Atom); +} + +# Delete atom... +sub _DeleteAtom { + my($This, $Atom) = @_; + + my($AtomID); + $AtomID = $Atom->GetID(); + $This->DeleteVertex($AtomID); + + return $This; +} + +# Delete atoms... +sub DeleteAtoms { + my($This, @Atoms) = @_; + + if (!@Atoms) { + carp "Warning: ${ClassName}->DeleteAtoms: No atoms added: Atoms list must be specified..."; + return undef; + } + my($Atom); + for $Atom (@Atoms) { + $This->DeleteAtom($Atom); + } + + return $This; +} + +# Is this atom present? +sub HasAtom { + my($This, $Atom) = @_; + + if (!defined $Atom) { + return 0; + } + if (!$Atom->HasProperty('Molecule')) { + # It's not in molecule... + return 0; + } + my($AtomID); + $AtomID = $Atom->GetID(); + if (!$This->HasVertex($AtomID)) { + # It's not in molecule... + return 0; + } + my($Molecule); + $Molecule = $Atom->GetProperty('Molecule'); + + return ($This->HasVertex($AtomID) && $This->GetID() == $Molecule->GetID()) ? 1 : 0; +} + +# Return an array of atoms. In scalar context, it returns number of atoms. Additionally, +# atoms array can be filtered by any user specifiable Atom class method... +# +sub GetAtoms { + my($This, $AtomCheckMethodName, $NegateMethodResult) = @_; + my(@Atoms, @AtomIDs); + + @Atoms = (); @AtomIDs = (); + + @AtomIDs = $This->GetVertices(); + if (!@AtomIDs) { + return wantarray ? @Atoms : scalar @Atoms; + } + + @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); + + if (!defined $AtomCheckMethodName) { + return wantarray ? @Atoms : scalar @Atoms; + } + $NegateMethodResult = (defined($NegateMethodResult) && $NegateMethodResult) ? 1 : 0; + + # Filter out atoms... + my($Atom, $KeepAtom, @FilteredAtoms); + @FilteredAtoms = (); + for $Atom (@Atoms) { + $KeepAtom = $NegateMethodResult ? (!$Atom->$AtomCheckMethodName()) : $Atom->$AtomCheckMethodName(); + if ($KeepAtom) { + push @FilteredAtoms, $Atom; + } + } + return wantarray ? @FilteredAtoms : scalar @FilteredAtoms; +} + +# Return an array of bonds. In scalar context, it returns number of bonds... +sub GetBonds { + my($This) = @_; + my(@Bonds, @EdgesAtomsIDs); + + @Bonds = (); @EdgesAtomsIDs = (); + + @EdgesAtomsIDs = $This->GetEdges(); + if (@EdgesAtomsIDs) { + @Bonds = $This->GetEdgesProperty('Bond', @EdgesAtomsIDs); + } + return wantarray ? @Bonds : scalar @Bonds; +} + +# Get number of atoms in molecule... +sub GetNumOfAtoms { + my($This) = @_; + my($NumOfAtoms); + + $NumOfAtoms = $This->GetAtoms(); + + return $NumOfAtoms; +} + +# Get number of bonds in molecule... +sub GetNumOfBonds { + my($This) = @_; + my($NumOfBonds); + + $NumOfBonds = $This->GetBonds(); + + return $NumOfBonds; +} + +# Get number of heavy atoms in molecule... +sub GetNumOfHeavyAtoms { + my($This) = @_; + + return $This->GetNumOfNonHydrogenAtoms(); +} + +# Get number of non-hydrogen atoms in molecule... +sub GetNumOfNonHydrogenAtoms { + my($This) = @_; + my($NumOfNonHydrogenAtoms, $Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + $NumOfNonHydrogenAtoms = 0; + for $Atom (@Atoms) { + if (!$Atom->IsHydrogen()) { + $NumOfNonHydrogenAtoms++; + } + } + return $NumOfNonHydrogenAtoms; +} + +# Get number of hydrogen atoms in molecule... +sub GetNumOfHydrogenAtoms { + my($This) = @_; + my($NumOfHydrogenAtoms, $Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + $NumOfHydrogenAtoms = 0; + for $Atom (@Atoms) { + if ($Atom->IsHydrogen()) { + $NumOfHydrogenAtoms++; + } + } + return $NumOfHydrogenAtoms; +} + +# Get number of missing hydrogen atoms in molecule... +sub GetNumOfMissingHydrogenAtoms { + my($This) = @_; + my($NumOfMissingHydrogenAtoms, $Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + $NumOfMissingHydrogenAtoms = 0; + for $Atom (@Atoms) { + if (!$Atom->IsHydrogen()) { + $NumOfMissingHydrogenAtoms += $Atom->GetNumOfMissingHydrogens(); + } + } + return $NumOfMissingHydrogenAtoms; +} + +# Add bond... +sub AddBond { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + if (!(defined($Atom1) && defined($Atom2))) { + carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be specified..."; + return undef; + } + if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) { + carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be present..."; + return undef; + } + if ($This->HasBond($Bond)) { + carp "Warning: ${ClassName}->AddBond: No bond added: Bond already exists..."; + return undef; + } + return $This->_AddBond($Bond); +} + +# Add bond... +sub _AddBond { + my($This, $Bond) = @_; + + # Assign bond to this molecule... + $Bond->_SetMolecule($This); + + # Add it to the graph as an edge... + my($Atom1, $Atom2, $AtomID1, $AtomID2); + ($Atom1, $Atom2) = $Bond->GetAtoms(); + $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); + $This->AddEdge($AtomID1, $AtomID2); + $This->SetEdgeProperty('Bond', $Bond, $AtomID1, $AtomID2); + + return $This; +} + +# Add Bonds... +sub AddBonds { + my($This, @Bonds) = @_; + + if (!@Bonds) { + carp "Warning: ${ClassName}->AddBonds: No bonds added: Bonds list must be specified..."; + return undef; + } + my($Bond); + for $Bond (@Bonds) { + $This->AddBond($Bond); + } + return $This; +} + +# Create a bond and add it to molecule... +sub NewBond { + my($This, %NamesAndValues) = @_; + my($Bond); + + $Bond = new Bond(%NamesAndValues); + $This->AddBond($Bond); + + return $Bond; +} + +# Delete a bond... +sub DeleteBond { + my($This, $Bond) = @_; + + if (!defined $Bond) { + carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond must be specified..."; + return undef; + } + # Does the bond exist in molecule? + if (!$This->HasBond($Bond)) { + carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond doesn't exist..."; + return undef; + } + return $This->_DeleteBond($Bond); +} + +# Delete bond... +sub _DeleteBond { + my($This, $Bond) = @_; + + my($Atom1, $Atom2, $AtomID1, $AtomID2); + ($Atom1, $Atom2) = $Bond->GetAtoms(); + $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); + $This->DeleteEdge($AtomID1, $AtomID2); + + return $This; +} + +# Delete bonds... +sub DeleteBonds { + my($This, @Bonds) = @_; + + if (!@Bonds) { + carp "Warning: ${ClassName}->DeleteBonds: No bonds added: Bonds list must be specified..."; + return undef; + } + my($Bond); + for $Bond (@Bonds) { + $This->DeleteBond($Bond); + } + + return $This; +} + +# Has bond... +sub HasBond { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) { + return 0; + } + if (!$Bond->HasProperty('Molecule')) { + # It's not in molecule... + return 0; + } + my($AtomID1, $AtomID2, $Molecule); + $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); + $Molecule = $Bond->GetMolecule(); + + return ($This->HasEdge($AtomID1, $AtomID2) && $This->GetID() == $Molecule->GetID()) ? 1 : 0; +} + +# Get atom neighbors... +sub _GetAtomNeighbors { + my($This, $Atom) = @_; + + my($AtomID, @Atoms, @AtomIDs); + + @Atoms = (); @AtomIDs = (); + $AtomID = $Atom->GetID(); + @AtomIDs = $This->GetNeighbors($AtomID); + if (@AtomIDs) { + @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); + } + return wantarray ? @Atoms : scalar @Atoms; +} + +# Get atom bonds... +sub _GetAtomBonds { + my($This, $Atom) = @_; + my($AtomID, @AtomIDs, @Bonds); + + @Bonds = (); @AtomIDs = (); + $AtomID = $Atom->GetID(); + @AtomIDs = $This->GetEdges($AtomID); + if (@AtomIDs) { + @Bonds = $This->GetEdgesProperty('Bond', @AtomIDs); + } + return wantarray ? @Bonds : scalar @Bonds; +} + +# Get bond to specified atom... +sub _GetBondToAtom { + my($This, $Atom1, $Atom2) = @_; + my($AtomID1, $AtomID2); + + $AtomID1 = $Atom1->GetID(); + $AtomID2 = $Atom2->GetID(); + + return $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2); +} + +# Are two specified atoms bonded? +sub _IsBondedToAtom { + my($This, $Atom1, $Atom2) = @_; + my($AtomID1, $AtomID2); + + $AtomID1 = $Atom1->GetID(); + $AtomID2 = $Atom2->GetID(); + + return $This->HasEdgeProperty('Bond', $AtomID1, $AtomID2); +} + +# Add hydrogens to each atoms in molecule and return total number of hydrogens added... +sub AddHydrogens { + my($This) = @_; + + return $This->_AddHydrogens(); +} + +# Add hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens added... +sub AddPolarHydrogens { + my($This) = @_; + my($PolarHydrogensOnly) = 1; + + return $This->_AddHydrogens($PolarHydrogensOnly); +} + +# Add all the hydrogens or hydrogens for polar atoms only... +# +# Note: +# . The current release of MayaChemTools doesn't assign any hydrogen positions. +# +sub _AddHydrogens { + my($This, $PolarHydrogensOnly) = @_; + my($Atom, $NumOfHydrogensAdded, $HydrogenPositionsWarning, @Atoms); + + if (! defined $PolarHydrogensOnly) { + $PolarHydrogensOnly = 0; + } + + $NumOfHydrogensAdded = 0; + @Atoms = $This->GetAtoms(); + $HydrogenPositionsWarning = 0; + + ATOM: for $Atom (@Atoms) { + if ($PolarHydrogensOnly) { + if (!$Atom->IsPolarAtom()) { + next ATOM; + } + } + $NumOfHydrogensAdded += $Atom->AddHydrogens($HydrogenPositionsWarning); + } + return $NumOfHydrogensAdded; +} + +# Delete all hydrogens atoms in molecule and return total number of hydrogens removed... +sub DeleteHydrogens { + my($This) = @_; + + return $This->_DeleteHydrogens(); +} + +# Delete hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens removed... +sub DeletePolarHydrogens { + my($This) = @_; + my($PolarHydrogensOnly) = 1; + + return $This->_DeleteHydrogens($PolarHydrogensOnly); +} + +# Delete all hydrogens atoms in molecule and return total number of hydrogens removed... +sub _DeleteHydrogens { + my($This, $PolarHydrogensOnly) = @_; + my($Atom, $NumOfHydrogensRemoved, @Atoms); + + if (! defined $PolarHydrogensOnly) { + $PolarHydrogensOnly = 0; + } + + $NumOfHydrogensRemoved = 0; + @Atoms = $This->GetAtoms(); + + ATOM: for $Atom (@Atoms) { + if ($PolarHydrogensOnly) { + if (!$Atom->IsPolarHydrogen()) { + next ATOM; + } + } + elsif (!$Atom->IsHydrogen()) { + next ATOM; + } + $This->_DeleteAtom($Atom); + $NumOfHydrogensRemoved++; + } + return $NumOfHydrogensRemoved; +} + +# Get molecular weight by summing up atomic weights of all the atoms... +sub GetMolecularWeight { + my($This, $IncludeMissingHydrogens) = @_; + my($MolecularWeight, $AtomicWeight, @Atoms, $Atom); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; + + $MolecularWeight = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicWeight = $Atom->GetAtomicWeight(); + if (defined $AtomicWeight) { + $MolecularWeight += $AtomicWeight; + } + } + + if (!$IncludeMissingHydrogens) { + return $MolecularWeight; + } + + # Account for missing hydrogen atoms... + my($NumOfMissingHydrogenAtoms); + + $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms(); + if ($NumOfMissingHydrogenAtoms) { + $MolecularWeight += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementAtomicWeight('H'); + } + + return $MolecularWeight; +} + +# Get exact mass by summing up exact masses of all the atoms... +sub GetExactMass { + my($This, $IncludeMissingHydrogens) = @_; + my($ExactMass, $AtomicMass, @Atoms, $Atom); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; + + $ExactMass = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicMass = $Atom->GetExactMass(); + if (defined $AtomicMass) { + $ExactMass += $AtomicMass; + } + } + + if (!$IncludeMissingHydrogens) { + return $ExactMass; + } + + # Account for missing hydrogen atoms... + my($NumOfMissingHydrogenAtoms); + + $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms(); + if ($NumOfMissingHydrogenAtoms) { + $ExactMass += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementMostAbundantNaturalIsotopeMass('H'); + } + + return $ExactMass; +} + +# Get net formal charge on the molecule using one of the following two methods: +# . Using explicitly set FormalCharge property +# . Adding up formal charge on each atom in the molecule +# +# Caveats: +# . FormalCharge is different from Charge property of the molecule which corresponds to +# sum of partial atomic charges explicitly set for each atom using a specific methodology. +# +sub GetFormalCharge { + my($This) = @_; + + # Is FormalCharge property explicitly set? + if ($This->HasProperty('FormalCharge')) { + return $This->GetProperty('FormalCharge'); + } + my($FormalCharge, $AtomicFormalCharge, @Atoms, $Atom); + + $FormalCharge = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicFormalCharge = $Atom->GetFormalCharge(); + if (defined $AtomicFormalCharge) { + $FormalCharge += $AtomicFormalCharge; + } + } + return $FormalCharge; +} + +# Get net charge on the molecule using one of the following two methods: +# . Using explicitly set Charge property +# . Adding up charge on each atom in the molecule +# +# Caveats: +# . FormalCharge is different from Charge property of the molecule which corresponds to +# sum of partial atomic charges explicitly set for each atom using a specific methodology. +# +sub GetCharge { + my($This) = @_; + + # Is Charge property explicitly set? + if ($This->HasProperty('Charge')) { + return $This->GetProperty('Charge'); + } + my($Charge, $AtomicCharge, @Atoms, $Atom); + + $Charge = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicCharge = $Atom->GetCharge(); + if (defined $AtomicCharge) { + $Charge += $AtomicCharge; + } + } + return $Charge; +} + +# Get total SpinMultiplicity for the molecule using one of the following two methods: +# . Using explicitly set SpinMultiplicity property +# . Adding up SpinMultiplicity on each atom in the molecule +# +# +sub GetSpinMultiplicity { + my($This) = @_; + + # Is SpinMultiplicity property explicitly set? + if ($This->HasProperty('SpinMultiplicity')) { + return $This->GetProperty('SpinMultiplicity'); + } + my($AtomicSpinMultiplicity, $SpinMultiplicity, @Atoms, $Atom); + + $SpinMultiplicity = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicSpinMultiplicity = $Atom->GetSpinMultiplicity(); + if (defined $AtomicSpinMultiplicity) { + $SpinMultiplicity += $AtomicSpinMultiplicity; + } + } + return $SpinMultiplicity; +} + +# Get total FreeRadicalElectrons for the molecule using one of the following two methods: +# . Using explicitly set FreeRadicalElectrons property +# . Adding up FreeRadicalElectrons on each atom in the molecule +# +# +sub GetFreeRadicalElectrons { + my($This) = @_; + + # Is FreeRadicalElectrons property explicitly set? + if ($This->HasProperty('FreeRadicalElectrons')) { + return $This->GetProperty('FreeRadicalElectrons'); + } + my($AtomicFreeRadicalElectrons, $FreeRadicalElectrons, @Atoms, $Atom); + + $FreeRadicalElectrons = 0; + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicFreeRadicalElectrons = $Atom->GetFreeRadicalElectrons(); + if (defined $AtomicFreeRadicalElectrons) { + $FreeRadicalElectrons += $AtomicFreeRadicalElectrons; + } + } + return $FreeRadicalElectrons; +} + +# Set valence model... +# +sub SetValenceModel { + my($This, $ValenceModel) = @_; + + if ($ValenceModel !~ /^(MDLValenceModel|DaylightValenceModel|InternalValenceModel|MayaChemToolsValenceModel)$/i) { + carp "Warning: ${ClassName}->SetValenceModel: The current release of MayaChemTools doesn't support the specified valence model $ValenceModel. Supported valence models: MDLValenceModel, DaylightValenceModel, InternalValenceModel or MayaChemToolsValenceModel. Using internal valence model..."; + $ValenceModel = 'InternalValenceModel'; + } + + $This->SetProperty('ValenceModel', $ValenceModel); + + return $This; +} + +# Get valence model... +# +sub GetValenceModel { + my($This) = @_; + + # Is ValenceModel property explicitly set? + if ($This->HasProperty('ValenceModel')) { + return $This->GetProperty('ValenceModel'); + } + + # Used internal valence model as default model... + return 'InternalValenceModel'; +} + +# Get molecular formula by collecting information about all atoms in the molecule and +# composing the formula using Hills ordering system: +# . C shows up first and H follows assuming C is present +# . All other standard elements are sorted alphanumerically +# . All other non-stanard atom symbols are also sorted alphanumerically and follow standard elements +# +# Caveats: +# . By default, missing hydrogens and nonelements are also included +# . Elements for disconnected fragments are combined into the same formula +# +# Handle formula generation for disconnected structures. e.g: molecule generated by +# [Na+].[O-]c1ccccc1 +# +sub GetMolecularFormula { + my($This, $IncludeMissingHydrogens, $IncludeNonElements) = @_; + my($MolecularFormula, $AtomSymbol, $ElementsCountRef, $NonElementsCountRef); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; + $IncludeNonElements = defined($IncludeNonElements) ? $IncludeNonElements : 1; + + # Get elements count and setup molecular formula... + ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens); + $MolecularFormula = ''; + + # Count C and H first... + if (exists $ElementsCountRef->{C} ) { + $MolecularFormula .= 'C' . ($ElementsCountRef->{C} > 1 ? $ElementsCountRef->{C} : ''); + delete $ElementsCountRef->{C}; + + if (exists $ElementsCountRef->{H} ) { + $MolecularFormula .= 'H' . ($ElementsCountRef->{H} > 1 ? $ElementsCountRef->{H} : ''); + delete $ElementsCountRef->{H}; + } + } + + # Sort elements... + for $AtomSymbol (sort {$a cmp $b} keys %{$ElementsCountRef}) { + $MolecularFormula .= $AtomSymbol . ($ElementsCountRef->{$AtomSymbol} > 1 ? $ElementsCountRef->{$AtomSymbol} : ''); + } + + # Sort non-elements... + if ($IncludeNonElements) { + for $AtomSymbol (sort {$a cmp $b} keys %{$NonElementsCountRef}) { + $MolecularFormula .= $AtomSymbol . ($NonElementsCountRef->{$AtomSymbol} > 1 ? $NonElementsCountRef->{$AtomSymbol} : ''); + } + } + + # Check formal charge... + my($FormalCharge); + $FormalCharge = $This->GetFormalCharge(); + if ($FormalCharge) { + # Setup formal charge string... + my($FormalChargeString); + if ($FormalCharge == 1 ) { + $FormalChargeString = "+"; + } + elsif ($FormalCharge == -1 ) { + $FormalChargeString = "-"; + } + else { + $FormalChargeString = ($FormalCharge > 0) ? ("+" . abs($FormalCharge)) : ("-" . abs($FormalCharge)); + } + $MolecularFormula = "${MolecularFormula}${FormalChargeString}"; + } + + return $MolecularFormula; +} + +# Count elements and non-elements in molecule and return references to hashes +# containing count of elements and non-elements respectively. By default, missing +# hydrogens are not added to the element hash. +# +# +sub GetElementsAndNonElements { + my($This, $IncludeMissingHydrogens) = @_; + my($Atom, $AtomicNumber, $AtomSymbol, $NumOfMissingHydrogens, @Atoms, %ElementsCount, %NonElementsCount); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0; + + %ElementsCount = (); %NonElementsCount = (); + $NumOfMissingHydrogens = 0; + + # Count elements and non elements... + @Atoms = $This->GetAtoms(); + for $Atom (@Atoms) { + $AtomicNumber = $Atom->GetAtomicNumber(); + $AtomSymbol = $Atom->GetAtomSymbol(); + if ($AtomicNumber) { + if (exists $ElementsCount{$AtomSymbol}) { + $ElementsCount{$AtomSymbol} += 1; + } + else { + $ElementsCount{$AtomSymbol} = 1; + } + if ($IncludeMissingHydrogens) { + $NumOfMissingHydrogens += $Atom->GetNumOfMissingHydrogens(); + } + } + else { + if (exists $NonElementsCount{$AtomSymbol}) { + $NonElementsCount{$AtomSymbol} += 1; + } + else { + $NonElementsCount{$AtomSymbol} = 1; + } + } + } + if ($IncludeMissingHydrogens && $NumOfMissingHydrogens) { + $AtomSymbol = 'H'; + if (exists $ElementsCount{$AtomSymbol}) { + $ElementsCount{$AtomSymbol} += $NumOfMissingHydrogens; + } + else { + $ElementsCount{$AtomSymbol} = $NumOfMissingHydrogens; + } + } + + return (\%ElementsCount, \%NonElementsCount); +} + +# Get number of element and non-elements in molecule. By default, missing +# hydrogens are not added to element count. +# +sub GetNumOfElementsAndNonElements { + my($This, $IncludeMissingHydrogens) = @_; + my($ElementCount, $NonElementCount, $Atom); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0; + + ($ElementCount, $NonElementCount) = (0, 0); + + ATOM: for $Atom ($This->GetAtoms()) { + if (!$Atom->GetAtomicNumber()) { + $NonElementCount++; + next ATOM; + } + # Process element... + $ElementCount++; + if ($IncludeMissingHydrogens) { + if (!$Atom->IsHydrogen()) { + $ElementCount += $Atom->GetNumOfMissingHydrogens(); + } + } + } + + return ($ElementCount, $NonElementCount); +} + +# Calculate elemental composition and return reference to arrays +# containing elements and their percent composition. +# +# Caveats: +# . By default, missing hydrogens are included +# . Non elemnents are ignored +# . Mass number are ignored +# +sub GetElementalComposition { + my($This, $IncludeMissingHydrogens) = @_; + my($MolecularFormula, $IncludeNonElements, $ElementsCountRef, $NonElementsCountRef, $ElementsRef, $ElementsCompositionRef); + + $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; + + $IncludeNonElements = 0; + ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens); + + $MolecularFormula = $This->GetMolecularFormula($IncludeMissingHydrogens, $IncludeNonElements); + + ($ElementsRef, $ElementsCompositionRef) = MolecularFormula::CalculateElementalComposition($MolecularFormula); + + return ($ElementsRef, $ElementsCompositionRef); +} + +# Using refernece to element and its composition arrays, format composition information +# as: Element: Composition;... +# +sub FormatElementalCompositionInformation { + my($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter) = @_; + my($This, $ElementsRef, $ElementCompositionRef, $Precision); + + if (_IsMolecule($FirstParameter)) { + ($This, $ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter); + } + else { + ($ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter); + } + my($FormattedInfo) = ''; + + if (!(defined($ElementsRef) && @{$ElementsRef})) { + carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements list is not defined or empty..."; + return undef; + } + if (!(defined($ElementCompositionRef) && @{$ElementCompositionRef})) { + carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements composition list is not defined or empty..."; + return undef; + } + + if (!defined $Precision) { + $Precision = 2; + } + + $FormattedInfo = MolecularFormula::FormatCompositionInfomation($ElementsRef, $ElementCompositionRef, $Precision); + + return $FormattedInfo; +} + +# Copy molecule and its associated data using Storable::dclone and update: +# +# o Atom references corresponding atoms and bonds objects +# o Bond object references +# +# Object IDs for Atoms and Bonds don't get changed. So there is no need to clear +# up any exisiting ring data attached to molecule via graph as vertex IDs. +# +sub Copy { + my($This) = @_; + my($NewMolecule, $Atom, $NewAtom, $AtomID, @Atoms, @AtomIDs, %AtomsIDsToNewAtoms); + + $NewMolecule = Storable::dclone($This); + + # Update atom references stored as vertex property... + + @Atoms = (); @AtomIDs = (); + %AtomsIDsToNewAtoms = (); + + @AtomIDs = $This->GetVertices(); + if (@AtomIDs) { + @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); + } + + for $Atom (@Atoms) { + $AtomID = $Atom->GetID(); + + # Setup a reference to copied atom object... + $NewAtom = $Atom->Copy(); + $AtomsIDsToNewAtoms{$AtomID} = $NewAtom; + + # Update atom reference to new atom object... + $NewMolecule->UpdateVertexProperty('Atom', $NewAtom, $AtomID); + } + + # Update bond object and bond atom references stored as edge property... + my($Index, $AtomID1, $AtomID2, $Bond, $NewBond, $NewAtom1, $NewAtom2, @EdgesAtomsIDs); + @EdgesAtomsIDs = (); + @EdgesAtomsIDs = $This->GetEdges(); + for ($Index = 0; $Index < $#EdgesAtomsIDs; $Index += 2) { + $AtomID1 = $EdgesAtomsIDs[$Index]; $AtomID2 = $EdgesAtomsIDs[$Index + 1]; + + # Get reference to bond object... + $Bond = $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2); + + # Make a new bond object and update its atom references... + $NewBond = $Bond->Copy(); + $NewAtom1 = $AtomsIDsToNewAtoms{$AtomID1}; + $NewAtom2 = $AtomsIDsToNewAtoms{$AtomID2}; + $NewBond->SetAtoms($NewAtom1, $NewAtom2); + + # Update bond object reference in the new molecule... + $NewMolecule->UpdateEdgeProperty('Bond', $NewBond, $AtomID1, $AtomID2); + } + + return $NewMolecule; +} + +# Get number of connected components... +# +sub GetNumOfConnectedComponents { + my($This) = @_; + my($NumOfComponents); + + $NumOfComponents = $This->GetConnectedComponentsVertices(); + + return $NumOfComponents; +} + +# Return a reference to an array containing molecules corresponding +# to connected components sorted in decreasing order of component size... +# +sub GetConnectedComponents { + my($This) = @_; + my($Index, @ComponentMolecules, @ConnectedComponents); + + @ConnectedComponents = (); + @ConnectedComponents = $This->GetConnectedComponentsVertices(); + @ComponentMolecules = (); + + for $Index (0 .. $#ConnectedComponents) { + push @ComponentMolecules, $This->_GetConnectedComponent(\@ConnectedComponents, $Index); + } + return @ComponentMolecules; +} + +# Return a reference to largest connected component as a molecule object... +# +sub GetLargestConnectedComponent { + my($This) = @_; + my($LargestComponentIndex, @ConnectedComponents); + + $LargestComponentIndex = 0; + @ConnectedComponents = (); + @ConnectedComponents = $This->GetConnectedComponentsVertices(); + + return $This->_GetConnectedComponent(\@ConnectedComponents, $LargestComponentIndex); +} + +# Return connected component as a molecule... +# +sub _GetConnectedComponent { + my($This, $ConnectedComponentsRef, $ComponentIndex) = @_; + my($ComponentMolecule); + + # Copy existing molecule... + $ComponentMolecule = $This->Copy(); + + # Delete all atoms besides the ones in specified component... + $ComponentMolecule->_DeleteConnectedComponents($ConnectedComponentsRef, $ComponentIndex); + + # Clear any deteced rings... + if ($ComponentMolecule->HasRings()) { + $ComponentMolecule->ClearRings(); + } + return $ComponentMolecule; +} + +# Delete atoms corresponding to all connected components except the one specified... +# +sub _DeleteConnectedComponents { + my($This, $ConnectedComponentsRef, $KeepComponentIndex) = @_; + my($Index, $AtomID); + + INDEX: for $Index (0 .. $#{$ConnectedComponentsRef}) { + if ($Index == $KeepComponentIndex) { + next INDEX; + } + for $AtomID (@{$ConnectedComponentsRef->[$Index]}) { + $This->DeleteVertex($AtomID); + } + } + return $This; +} + +# Return an array containing references to atom arrays corresponding to atoms of +# connected components sorted in order of their decreasing size... +# +sub GetConnectedComponentsAtoms { + my($This) = @_; + my($Index, @ComponentsAtoms, @ConnectedComponents); + + @ConnectedComponents = (); + @ConnectedComponents = $This->GetConnectedComponentsVertices(); + + @ComponentsAtoms = (); + for $Index (0 .. $#ConnectedComponents) { + my(@ComponentAtoms); + + @ComponentAtoms = (); + @ComponentAtoms = $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $Index); + push @ComponentsAtoms, \@ComponentAtoms; + } + return @ComponentsAtoms; +} + +# Return an array containing atoms correspondig to largest connected component... +# +sub GetLargestConnectedComponentAtoms { + my($This) = @_; + my($LargestComponentIndex, @ConnectedComponents); + + $LargestComponentIndex = 0; + @ConnectedComponents = (); + @ConnectedComponents = $This->GetConnectedComponentsVertices(); + + return $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $LargestComponentIndex); +} + +# Return an array containing atoms corresponding to specified connected component... +# +sub _GetConnectedComponentAtoms { + my($This, $ConnectedComponentsRef, $ComponentIndex) = @_; + my($AtomID, @AtomIDs, @ComponentAtoms); + + @ComponentAtoms = (); + @AtomIDs = (); + + for $AtomID (@{$ConnectedComponentsRef->[$ComponentIndex]}) { + push @AtomIDs, $AtomID; + } + @ComponentAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); + + return @ComponentAtoms; +} + +# Except for the largest connected component, delete atoms corresponding to all other +# connected components... +# +sub KeepLargestComponent { + my($This) = @_; + my($LargestComponentIndex, @ConnectedComponents); + + @ConnectedComponents = (); + @ConnectedComponents = $This->GetConnectedComponentsVertices(); + if (@ConnectedComponents == 1) { + return $This; + } + $LargestComponentIndex = 0; + $This->_DeleteConnectedComponents(\@ConnectedComponents, $LargestComponentIndex); + + # Clear any deteced rings... + if ($This->HasRings()) { + $This->ClearRings(); + } + + return $This; +} + +# Get an array of topologically sorted atoms starting from a specified atom or +# an arbitrary atom in the molecule... +# +sub GetTopologicallySortedAtoms { + my($This, $StartAtom) = @_; + my(@SortedAtoms); + + @SortedAtoms = (); + if (defined($StartAtom) && !$This->HasAtom($StartAtom)) { + carp "Warning: ${ClassName}->_GetTopologicallySortedAtoms: No atoms retrieved: Start atom doesn't exist..."; + return @SortedAtoms; + } + my($StartAtomID, @AtomIDs); + + @AtomIDs = (); + $StartAtomID = defined($StartAtom) ? $StartAtom->GetID() : undef; + + @AtomIDs = $This->GetTopologicallySortedVertices($StartAtomID); + @SortedAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); + + return @SortedAtoms; +} + +# Detect rings in molecule... +# +sub DetectRings { + my($This) = @_; + + # Use graph method to detect all cycles and associate 'em to graph as graph + # and vertex properties... + return $This->DetectCycles(); +} + +# Clear rings in molecule... +# +sub ClearRings { + my($This) = @_; + + # Use graph method to clear all cycles... + $This->ClearCycles(); + + return $This; +} + +# Setup rings type paths to use during all ring related methods. Possible values: +# Independent or All. Default is to use Independent rings. +# +sub SetActiveRings { + my($This, $RingsType) = @_; + + if (!defined $This->SetActiveCyclicPaths($RingsType)) { + return undef; + } + return $This; +} + +# Is it a supported aromaticity model? +# +sub IsSupportedAromaticityModel { + my($FirstParameter, $SecondParameter) = @_; + my($This, $AromaticityModel); + + if (_IsMolecule($FirstParameter)) { + ($This, $AromaticityModel) = ($FirstParameter, $SecondParameter); + } + else { + ($This, $AromaticityModel) = (undef, $FirstParameter); + } + + return exists $CanonicalAromaticityModelNamesMap{lc($AromaticityModel)} ? 1 : 0; +} + +# Get a list of supported aromaticity model names... +# +sub GetSupportedAromaticityModels { + return (sort values %CanonicalAromaticityModelNamesMap); +} + +# Set aromaticity model... +# +sub SetAromaticityModel { + my($This, $AromaticityModel) = @_; + + if (!$This->IsSupportedAromaticityModel($AromaticityModel)) { + my(@SupportedModels) = $This->GetSupportedAromaticityModels(); + + carp "Warning: ${ClassName}->SetAromaticityModel: The current release of MayaChemTools doesn't support the specified aromaticity model $AromaticityModel. Supported aromaticity models defined in AromaticityModelsData.csv file are: @SupportedModels . Using MayaChemToolsAromaticityModel..."; + $AromaticityModel = 'MayaChemToolsAromaticityModel'; + } + + $This->SetProperty('AromaticityModel', $AromaticityModel); + + return $This; +} + +# Get aromaticity model... +# +sub GetAromaticityModel { + my($This) = @_; + + # Is ValenceModel property explicitly set? + if ($This->HasProperty('AromaticityModel')) { + return $This->GetProperty('AromaticityModel'); + } + + # Used internal aromaticity model as default model... + return 'MayaChemToolsAromaticityModel'; +} + +# Identify aromatic rings and ring systems in a molecule and set aromaticity for +# corresponding atoms and bonds. +# +# What is aromaticity? [ Ref 124 ] It's in the code of the implementer, did you +# say? Agree. The implementation of aromaticity varies widely across different +# packages [ Ref 125 ]; additionally, the implementation details are not always +# completely available, and it's not possible to figure out the exact implementation +# of aromaticity across various packages. Using the publicly available information, +# however, one can try to reproduce the available results to the extent possible, +# along with parameterizing all the control parameters used to implement different +# aromaticity models, and that's exactly what the current release of MayaChemTools +# does. +# +# The implementation of aromaticity corresponding to various aromaticity models in +# MayaChemTools package is driven by an external CSV file AromaticityModelsData.csv, +# which is distributed with the package and is available in lib/data directory. The CSV +# files contains names of supported aromaticity models, along with various control +# parameters and their values. This file is loaded and processed during instantiation +# of Molecule class and data corresponding to specific aromaticity model are used +# to detect aromaticity for that model. Any new aromaticity model added to the +# aromaticity data file, using different combinations of values for existing control +# parameters would work without any changes to the code; the addition of any new +# control parameters, however, requires its implementation in the code used to +# calculate number of pi electrons available towards delocalization in a ring or ring +# systems. +# +# The current release of MayaChemTools package supports these aromaticity +# models: MDLAromaticityModel, TriposAromaticityModel, MMFFAromaticityModel, +# ChemAxonBasicAromaticityModel, ChemAxonGeneralAromaticityModel, +# DaylightAromaticityModel, MayaChemToolsAromaticityModel. +# +# The current list of control parameters available to detect aromaticity corresponding +# to different aromaticity models are: AllowHeteroRingAtoms, HeteroRingAtomsList, +# AllowExocyclicDoubleBonds, AllowHomoNuclearExocyclicDoubleBonds, +# AllowElectronegativeRingAtomExocyclicDoubleBonds, AllowRingAtomFormalCharge, +# AllowHeteroRingAtomFormalCharge, MinimumRingSize. The values for these control +# parameters are specified in AromaticityModelsData.csv file. +# +# Although definition of aromaticity differs across various aromaticity models, a ring +# or a ring system containing 4n + 2 pi electrons (Huckel's rule) corresponding to +# alternate single and double bonds, in general, is considered aromatic. +# +# The available valence free electrons on heterocyclic ring atoms, involved in two single +# ring bonds, are also allowed to participate in pi electron delocalizaiton for most of +# the supported aromaticity models. +# +# The presence of exocyclic terminal double bond on ring atoms involved in pi electron +# delocalization is only allowed for some of the aromaticity models. Additionally, the type +# atoms involved in exocyclic terminal double bonds may result in making a ring or ring +# system non-aromatic. +# +sub DetectAromaticity { + my($This) = @_; + + # Delete aromaticity property for atoms and bonds... + $This->DeleteAromaticity(); + + # Any ring out there... + if (!$This->HasRings()) { + return $This; + } + + if ($This->HasFusedRings()) { + $This->_DetectAromaticityUsingFusedRingSets(); + } + else { + $This->_DetectAromaticityUsingIndividualRings(); + } + return $This; +} + +# Go over all rings and set aromaticity property for corresponding ring atoms +# and bonds involved in aromatic rings... +# +sub _DetectAromaticityUsingIndividualRings { + my($This) = @_; + + return $This->_DetectRingsAromaticity($This->GetRings()); +} + +# For each fused ring set, detect aromaticity by treating all of its ring as one aromatic +# system for counting pi electrons to satisfy Huckel's rule; In case of a failure, rings in +# fused set are treated individually for aromaticity detection. Additionally, non-fused +# rings are handled on their own during aromaticity detection. +# +# Note: +# . pi electrons in common bonds involved in fused ring sets are only counted once. +# +# +sub _DetectAromaticityUsingFusedRingSets { + my($This) = @_; + my($Index, $RingAtomsRef, $FusedRingSetRef, $FusedRingSetsRef, $NonFusedRingsRef, @FusedRingSetIsAromatic); + + ($FusedRingSetsRef, $NonFusedRingsRef) = $This->GetFusedAndNonFusedRings(); + + @FusedRingSetIsAromatic = (); + RINGSET: for $Index (0 .. $#{$FusedRingSetsRef}) { + $FusedRingSetRef = $FusedRingSetsRef->[$Index]; + $FusedRingSetIsAromatic[$Index] = 0; + + my($NumOfPiElectronsInRingSet, $NumOfPiElectronsInRing, $Bond, $BondID, %FusedRingSetsBondsMap, %FusedRingSetsBondsVisitedMap, %FusedRingBondsMap); + + $NumOfPiElectronsInRingSet = 0; + + %FusedRingSetsBondsMap = (); + %FusedRingSetsBondsVisitedMap = (); + %FusedRingBondsMap = (); + + # Setup a bond ID map for all bonds in fused ring set and another one + # for bonds involved in more than one ring... + # + for $RingAtomsRef (@{$FusedRingSetRef}) { + for $Bond ($This->GetRingBonds(@{$RingAtomsRef})) { + $BondID = $Bond->GetID(); + $FusedRingSetsBondsMap{$BondID} = $BondID; + + if ($Bond->GetNumOfRings() == 2) { + $FusedRingBondsMap{$BondID} = $BondID; + } + } + } + + for $RingAtomsRef (@{$FusedRingSetRef}) { + my(@RingBonds); + + @RingBonds = (); + @RingBonds = $This->GetRingBonds(@{$RingAtomsRef}); + $NumOfPiElectronsInRing = $This->_GetNumOfPiElectronsAvailableForDelocalization($RingAtomsRef, \@RingBonds, \%FusedRingSetsBondsMap, \%FusedRingSetsBondsVisitedMap, \%FusedRingBondsMap); + + if (!$NumOfPiElectronsInRing) { + next RINGSET; + } + $NumOfPiElectronsInRingSet += $NumOfPiElectronsInRing; + } + if ($This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectronsInRingSet)) { + $FusedRingSetIsAromatic[$Index] = 1; + } + } + + # Set atom and bond aromatic flags for ring sets whose pi electrons satisfy Huckel's rule; otherwise, + # treat rings in a ring set as individual rings for detecting aromaticity... + for $Index (0 .. $#{$FusedRingSetsRef}) { + $FusedRingSetRef = $FusedRingSetsRef->[$Index]; + if ($FusedRingSetIsAromatic[$Index]) { + $This->_SetRingsAromaticity(@{$FusedRingSetRef}); + } + else { + $This->_DetectRingsAromaticity(@{$FusedRingSetRef}); + } + } + + $This->_DetectRingsAromaticity(@{$NonFusedRingsRef}); + + return $This; +} + +# Detect and set aromaticity for rings... +# +sub _DetectRingsAromaticity { + my($This, @Rings) = @_; + my($RingAtom, $RingBond, $RingAtomsRef); + + RING: for $RingAtomsRef (@Rings) { + if (!$This->_CheckRingAromaticity(@{$RingAtomsRef})) { + next RING; + } + $This->_SetRingAromaticity(@{$RingAtomsRef}); + } + return $This; +} + +# Set aromatic property for all all atoms and bonds involved in all specified rings.. +# +sub _SetRingsAromaticity { + my($This, @Rings) = @_; + my($RingAtomsRef ); + + for $RingAtomsRef (@Rings) { + $This->_SetRingAromaticity(@{$RingAtomsRef}); + } + return $This; +} + +# Set aromatic property for all all atoms and bonds involved in ring.. +# +sub _SetRingAromaticity { + my($This, @RingAtoms) = @_; + my($RingAtom, $RingBond); + + for $RingAtom (@RingAtoms) { + $RingAtom->SetAromatic(1); + } + for $RingBond ($This->GetRingBonds(@RingAtoms)) { + $RingBond->SetAromatic(1); + } + return $This; +} + + +# For a ring to be an aromatic ring, all of its atoms must have aromatic property +# set. +# +sub IsRingAromatic { + my($This, @RingAtoms) = @_; + my($RingAtom); + + for $RingAtom (@RingAtoms) { + if (!$RingAtom->IsAromatic()) { + return 0; + } + } + return 1; +} + +# Delete aromatic property for all atoms and bonds... +# +sub DeleteAromaticity { + my($This) = @_; + + return $This->_DeleteAtomsAndBondsAromaticity(); +} + +# Check ring aromaticity... +# +sub _CheckRingAromaticity { + my($This, @RingAtoms) = @_; + my($NumOfPiElectrons, $BondID, @RingBonds); + + @RingBonds = (); + @RingBonds = $This->GetRingBonds(@RingAtoms); + + $NumOfPiElectrons = $This->_GetNumOfPiElectronsAvailableForDelocalization(\@RingAtoms, \@RingBonds); + + return $This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectrons); +} + +# Get number of pi electrons available for delocalizaiton in a ring or ring system... +# +sub _GetNumOfPiElectronsAvailableForDelocalization { + my($This, $RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef, $FusedRingSetsBondsVisitedMapRef, $FusedRingBondsMapRef) = @_; + my($AromaticityModelName, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef, $NumOfConjugatedDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfRingAtomElectronPairs, $NumOfRingBondsProcessed, $NumOfPiElectrons, $Index, $RingBond, $RingAtom, $RingAtomSymbol, $BondOrder, $RingAtomID, $RingBondID, $PreviousIndex, $PreviousRingBond, $ExcludeFreeRadicalElectrons, %ElectronPairContributionProcessedMap); + + $AromaticityModelName = $CanonicalAromaticityModelNamesMap{lc($This->GetAromaticityModel())}; + $AromaticityModelDataRef = \%{$AromaticityModelsDataMap{$AromaticityModelName}}; + + # Perform an intial check for potential atomatic ring atoms.. + if (!$This->_CheckRingAtomsForPotentialAromaticity($RingAtomsRef, $RingBondsRef, $AromaticityModelDataRef)) { + return 0; + } + + $ExocyclicDoubleBondsDataMapRef = undef; + $ExcludeFreeRadicalElectrons = 1; + + %ElectronPairContributionProcessedMap = (); + + ($NumOfPiElectrons, $NumOfRingBondsProcessed, $NumOfConjugatedDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfRingAtomElectronPairs) = ('0') x 5; + + # Go over ring atoms and bonds to check their participation in aromaticity and count + # pi electrons available for delocalization corresponding to various aromaticity models... + # + RINGBOND: for $Index (0 .. $#{$RingBondsRef}) { + $RingBond = $RingBondsRef->[$Index]; + $RingAtom = $RingAtomsRef->[$Index]; + $BondOrder = $RingBond->GetBondOrder(); + + # Is this ring bond part of a fused ring system which has been already processed? + if (defined($FusedRingSetsBondsVisitedMapRef) && $RingBond->GetNumOfRings() == 2) { + $RingBondID = $RingBond->GetID(); + if (exists $FusedRingSetsBondsVisitedMapRef->{$RingBondID}) { + next RINGBOND; + } + $FusedRingSetsBondsVisitedMapRef->{$RingBondID} = $RingBondID; + } + $NumOfRingBondsProcessed++; + + # For first ring, previous ring bond corrresponds to last ring bond... + $PreviousIndex = $Index ? ($Index -1) : $#{$RingBondsRef}; + $PreviousRingBond = $RingBondsRef->[$PreviousIndex]; + + # Check for presence of alternate single/double bond configuration, and pesence of + # hetero atoms with two single ring bonds along with any exocyclic double bonds... + # + BONDORDER: { + # Is current ring double bond in an alternate single/double bond configuration? + if ($BondOrder == 2) { + if ($PreviousRingBond->GetBondOrder() != 1) { + return 0; + } + $NumOfConjugatedDoubleBonds += 1; + last BONDORDER; + } + + # Is current ring bond order correspond to an explicit aromatic bond? + if ($BondOrder == 1.5) { + if ($PreviousRingBond->GetBondOrder() != 1.5) { + return 0; + } + $NumOfExplicitAromaticBonds += 1; + last BONDORDER; + } + + # Check for potential hetero atoms involved in two single ring bonds along + # with any terminal exocyclic bonds... + if ($BondOrder == 1) { + if ($PreviousRingBond->GetBondOrder() != 1) { + # Part of a conjugated system... + last BONDORDER; + } + + # Identify any exocylic bonds on rings atoms... + if (!defined $ExocyclicDoubleBondsDataMapRef) { + $ExocyclicDoubleBondsDataMapRef = $This->_IdentifyRingAtomsInvolvedInExocyclicDoubleBonds($RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef); + } + + # Is current ring atom part of an allowed exocyclic terminal bond? + if (!$This->_CheckPotentialAromaticRingAtomForExocylicDoubleBonds($RingAtom, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef)) { + return 0; + } + + # Is it allowed to have any formal charge? + if (!$This->_CheckPotentialAromaticRingAtomForFormalCharge($RingAtom, $AromaticityModelDataRef)) { + return 0; + } + + # It it an allowed hetero ring atom or a carbon atom? + if (!$This->_CheckPotentialAromaticRingAtomForAllowedHeteroAtoms($RingAtom, $AromaticityModelDataRef)) { + return 0; + } + + $RingAtomID = $RingAtom->GetID(); + $ElectronPairContributionProcessedMap{$RingAtomID} = $RingAtomID; + + # Is it able to donate a pair for electrons towards pi electron delocalization? + if ($RingAtom->GetValenceFreeElectrons($ExcludeFreeRadicalElectrons) >= 2) { + # Possibilites: + # . Hetero atom with or without formal charge and an available electron pair + # . Carbon atom with -ve formal charge and with an available electron pair + # + $NumOfRingAtomElectronPairs += 1; + } + else { + # Is ring atom involved in two single bonds without any electron pair allowed? + if (!$This->_AllowRingAtomInTwoSingleBondsWithoutElectronPair($RingAtom, $RingBond, $PreviousRingBond, $ExocyclicDoubleBondsDataMapRef, $FusedRingBondsMapRef)) { + return 0; + } + } + last BONDORDER; + } + + # Any other type of ring atom/bond is not allowed to contribute towards pi electron count + # and caused loss of aromaticity... + return 0; + } + } + + # Check for any electron pair contributions towards pi electron delocalization due to + # -ve formal charge on ring atoms which haven't been already processed and part of + # conjugated single/double bond system... + # + $NumOfRingAtomElectronPairs += $This->_GetElectronPairsContributionFromConjugatedRingAtoms($RingAtomsRef, $RingBondsRef, $ExcludeFreeRadicalElectrons, $AromaticityModelDataRef, \%ElectronPairContributionProcessedMap); + + # Setup pi electron count available for delocalization... + COUNT: { + if ($NumOfExplicitAromaticBonds == $NumOfRingBondsProcessed) { + # Each aromatic bond contribute one electron towards pi electron delocalization... + $NumOfPiElectrons = $NumOfExplicitAromaticBonds; + last COUNT; + } + + # Each conjugated double bond contribute two electrons towards pi electron delocalization... + $NumOfPiElectrons = 2*$NumOfConjugatedDoubleBonds + 2*$NumOfRingAtomElectronPairs; + } + + return $NumOfPiElectrons; +} + +# Check ring atoms for their potential participation in aromatic systems.. +# +sub _CheckRingAtomsForPotentialAromaticity { + my($This, $RingAtomsRef, $RingBondsRef, $AromaticityModelDataRef) = @_; + my($Index, $RingBond, $RingAtom); + + # Check availability of ring atoms and bonds... + if (!(defined($RingAtomsRef) && @{$RingBondsRef})) { + return 0; + } + + # Is there any minimum ring size limit? + if ($AromaticityModelDataRef->{MinimumRingSize}) { + if (@{$RingAtomsRef} < $AromaticityModelDataRef->{MinimumRingSize}) { + return 0; + } + } + + # Make sure ring bond order is not greater than 2 and ring atom is not connected to more + # than 3 other atoms to eliminate any non sp2 carbon atoms and still allow for hetero atoms + # to contrbute towards electron delocalization... + # + for $Index (0 .. $#{$RingBondsRef}) { + $RingBond = $RingBondsRef->[$Index]; + $RingAtom = $RingAtomsRef->[$Index]; + + if (($RingBond->GetBondOrder() > 2) || ($RingAtom->GetNumOfBonds() + $RingAtom->GetNumOfMissingHydrogens()) > 3) { + return 0; + } + } + + return 1; +} + +# Identify any exocylic double bonds on ring atoms... +# +sub _IdentifyRingAtomsInvolvedInExocyclicDoubleBonds { + my($This, $RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef) = @_; + my($Index, $RingAtom, $RingBond, $RingAtomID, $Bond, $BondID, $BondedAtom, $RingBondsMapRef, %RingBondsMap, %ExocyclicDoubleBondsDataMap); + + # Setup a ring bond map to process exocyclic bonds... + $RingBondsMapRef = undef; + %RingBondsMap = (); + + if (defined $FusedRingSetsBondsMapRef) { + $RingBondsMapRef = $FusedRingSetsBondsMapRef; + } + else { + for $BondID (map { $_->GetID() } @{$RingBondsRef}) { + $RingBondsMap{$BondID} = $BondID; + } + $RingBondsMapRef = \%RingBondsMap; + } + + # Intialize exocyclic terminal double bond data... + %ExocyclicDoubleBondsDataMap = (); + %{$ExocyclicDoubleBondsDataMap{RingAtomID}} = (); + + for $Index (0 .. $#{$RingBondsRef}) { + $RingBond = $RingBondsRef->[$Index]; + $RingAtom = $RingAtomsRef->[$Index]; + + $RingAtomID = $RingAtom->GetID(); + + BOND: for $Bond ($RingAtom->GetBonds()) { + if ($Bond->GetBondOrder != 2) { + next BOND; + } + + # Is it part of ring or ring system under consideration? + if (exists $RingBondsMapRef->{$Bond->GetID()}) { + next BOND; + } + + # Is bonded atom in a ring or a non-terminal atom? + $BondedAtom = $Bond->GetBondedAtom($RingAtom); + if ($BondedAtom->IsInRing() || !$BondedAtom->IsTerminal() ) { + next BOND; + } + + # Track exocyclic terminal double bond information... + if (!exists $ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}) { + @{$ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}} = (); + } + push @{$ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}}, $BondedAtom; + } + } + + return \%ExocyclicDoubleBondsDataMap; +} + +# Check to see whether ring atoms are allowed to participate in exocyclic terminal double +# bonds... +# +sub _CheckPotentialAromaticRingAtomForExocylicDoubleBonds { + my($This, $RingAtom, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef) = @_; + my($RingAtomID, $ExocyclicTerminalAtom, $RingAtomElectronegativity, $TerminalAtomElectronagativity); + + $RingAtomID = $RingAtom->GetID(); + + # Is it part of an exocyclic terminal double bond? + if (!exists $ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}) { + return 1; + } + + # Are exocyclic terminal double bonds allowed? + if (!$AromaticityModelDataRef->{AllowExocyclicDoubleBonds}) { + return 0; + } + + # Are there multiple exocyclic double bonds? + if (@{$ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}} > 1) { + return 0; + } + ($ExocyclicTerminalAtom) = @{$ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}}; + + # Are homo nuclear exocyclic terminal double bonds allowed? + if (!$AromaticityModelDataRef->{AllowHomoNuclearExocyclicDoubleBonds}) { + if ($RingAtom->GetAtomicNumber() == $ExocyclicTerminalAtom->GetAtomicNumber()) { + return 0; + } + } + + # Are ring atoms with higher electronegativity allowed in exocyclic double bonds? + if (!$AromaticityModelDataRef->{AllowElectronegativeRingAtomExocyclicDoubleBonds}) { + $RingAtomElectronegativity = PeriodicTable::GetElementPaulingElectronegativity($RingAtom->GetAtomicNumber()); + $TerminalAtomElectronagativity = PeriodicTable::GetElementPaulingElectronegativity($ExocyclicTerminalAtom->GetAtomicNumber()); + + if ($RingAtomElectronegativity && $TerminalAtomElectronagativity) { + if ($RingAtomElectronegativity > $TerminalAtomElectronagativity) { + return 0; + } + } + } + + return 1; +} + +# +# Check for any formal charge participation into electron delocalization... +# +sub _CheckPotentialAromaticRingAtomForFormalCharge { + my($This, $RingAtom, $AromaticityModelDataRef) = @_; + my($FormalCharge); + + # Does atom has any formal charge? + $FormalCharge = $RingAtom->GetFormalCharge(); + if (!$FormalCharge) { + return 1; + } + + # Are ring atoms with formal charge allowed to participate in electron delocalization? + if (!$AromaticityModelDataRef->{AllowRingAtomFormalCharge}) { + return 0; + } + + # Are hetero ring atoms with formal charge allowed to participate in electron delocalization? + if (!$RingAtom->IsCarbon()) { + if (!$AromaticityModelDataRef->{AllowHeteroRingAtomFormalCharge}) { + return 0; + } + } + + return 1; +} + +# +# Check ring atoms for allowed hetero atoms... +# +sub _CheckPotentialAromaticRingAtomForAllowedHeteroAtoms { + my($This, $RingAtom, $AromaticityModelDataRef) = @_; + my($RingAtomSymbol); + + # Is it a Carbon atom? + if ($RingAtom->IsCarbon()) { + return 1; + } + + # Are heteroatoms allowed? + if (!$AromaticityModelDataRef->{AllowHeteroRingAtoms}) { + return 0; + } + + # Is it an allowed hetero atom? + $RingAtomSymbol = $RingAtom->GetAtomSymbol(); + if (!exists $AromaticityModelDataRef->{HeteroRingAtomsListMapRef}->{$RingAtomSymbol}) { + return 0; + } + + return 1; +} + +# Check for any electron pair contributions toward pi electron delocalization due to +# -ve formal charge on ring atoms which haven't been already processed and part of +# conjugated single/double bond system... +# +sub _GetElectronPairsContributionFromConjugatedRingAtoms { + my($This, $RingAtomsRef, $RingBondsRef, $ExcludeFreeRadicalElectrons, $AromaticityModelDataRef, $ElectronPairContributionProcessedMapRef) = @_; + my($Index, $RingBond, $RingAtom, $NumOfRingAtomElectronPairs, $RingAtomID); + + # Is formal charge allowed on ring atoms? + if (!$AromaticityModelDataRef->{AllowRingAtomFormalCharge}) { + return 0; + } + + $NumOfRingAtomElectronPairs = 0; + + # Process ring atoms... + RINGBOND: for $Index (0 .. $#{$RingBondsRef}) { + $RingBond = $RingBondsRef->[$Index]; + $RingAtom = $RingAtomsRef->[$Index]; + $RingAtomID = $RingAtom->GetID(); + + # Is is already processed? + if (exists $ElectronPairContributionProcessedMapRef->{$RingAtomID}) { + next RINGBOND; + } + $ElectronPairContributionProcessedMapRef->{$RingAtomID} = $RingAtomID; + + # Is it allowed to have any formal charge? + if (!$This->_CheckPotentialAromaticRingAtomForFormalCharge($RingAtom, $AromaticityModelDataRef)) { + next RINGBOND; + } + + # It it an allowed hetero ring atom or a carbon atom? + if (!$This->_CheckPotentialAromaticRingAtomForAllowedHeteroAtoms($RingAtom, $AromaticityModelDataRef)) { + next RINGBOND; + } + + # It is an atom with -ve formal charge? + if ($RingAtom->GetFormalCharge() >= 0) { + next RINGBOND; + } + + # Is it able to donate a pair for electrons towards pi electron delocalization? + if ($RingAtom->GetValenceFreeElectrons($ExcludeFreeRadicalElectrons) < 2) { + next RINGBOND; + } + $NumOfRingAtomElectronPairs += 1; + } + + return $NumOfRingAtomElectronPairs; +} + +# Check for ring atoms involved in two single ring bonds without any available electron +# pair which are allowed to participate in aromatic system, after all other checks +# corresponding to specified aromaticity models have already been performed... +# +sub _AllowRingAtomInTwoSingleBondsWithoutElectronPair { + my($This, $RingAtom, $RingBond, $PreviousRingBond, $ExocyclicDoubleBondsDataMapRef, $FusedRingBondsMapRef) = @_; + + ALLOWRINGATOM: { + if (exists $ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtom->GetID()}) { + # Ring atom in an exocylic terminal double bond without any available electron pair... + last ALLOWRINGATOM; + } + + if ($RingAtom->GetFormalCharge() > 0) { + # Ring atom with positive formal charge without any available electron pair... + last ALLOWRINGATOM; + } + + if (defined $FusedRingBondsMapRef && (exists $FusedRingBondsMapRef->{$RingBond->GetID()} || exists $FusedRingBondsMapRef->{$PreviousRingBond->GetID()})) { + # Ring atom involved in fused ring bond, which might end up being part of a conjugated + # system in another fused ring... + last ALLOWRINGATOM; + } + + # Ring atom in any other environment is not allowed... + return 0; + } + + return 1; +} + +# Do pi electrons satify huckel's rule: Number of pi electrons correspond to 4n + 2 where +# n is a positive integer... +# +sub _DoPiElectronSatifyHuckelsRule { + my($This, $NumOfPiElectrons) = @_; + + $NumOfPiElectrons = $NumOfPiElectrons - 2; + + return ($NumOfPiElectrons > 0) ? (($NumOfPiElectrons % 4) ? 0 : 1) : 0; +} + +# Delete aromatic property for all atoms and bonds... +# +sub _DeleteAtomsAndBondsAromaticity { + my($This) = @_; + my($Atom, $Bond); + + for $Atom ($This->GetAtoms()) { + $Atom->DeleteAromatic(); + } + for $Bond ($This->GetBonds()) { + $Bond->DeleteAromatic(); + } + return $This; +} + +# Kekulize marked ring and non-ring aromatic atoms in a molecule... +# +sub KekulizeAromaticAtoms { + my($This) = @_; + + if (!$This->_KekulizeAromaticAtomsInRings()) { + return 0; + } + + if (!$This->_KekulizeAromaticAtomsNotInRings()) { + return 0; + } + + return 1; +} + +# Kekulize marked aromatic atoms in rings and fused ring sets... +# +sub _KekulizeAromaticAtomsInRings { + my($This) = @_; + + if (!$This->HasRings()) { + # Nothing to do... + return 1; + } + + if (!$This->HasAromaticAtomsInRings()) { + # Nothing to do... + return 1; + } + + # Identify fully aromatic fused and individual rings along with any partially aromatic ring components + # using marked aromatic atoms in a molecule and kekulize them as individual stes... + # + my($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = (undef) x 3; + if ($This->HasFusedRings()) { + ($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = $This->_GetFusedAndNonFusedRingsContainingAromaticAtoms(); + } + else { + ($AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = $This->_GetIndividualRingsContainingAromaticAtoms(); + } + + return $This->_KekulizeCompleteAndPartialAromaticRings($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef); +} + +# Identify fully aromatic fused and individual rings along with any partially aromatic ring components +# using marked aromatic atoms in a molecule... +# +sub _GetFusedAndNonFusedRingsContainingAromaticAtoms { + my($This) = @_; + my($Index, $SetAtomsCount, $SetAromaticAtomsCount, $FusedRingSetRef, $FusedRingSetsRef, $NonFusedRingsRef, $IndividualRingsRef, $RingAtomsRef, $RingAtomsCount, $AromaticAtomsCount, $RingAtom, $NonFusedFullyAromaticRingsRef, $NonFusedPartiallyAromaticRingComponentsRef, $PartiallyAromaticRingComponentsRef, @FullyAromaticFusedRingSets, @PotentialFullyAromaticRings, @FullyAromaticRings, @PotentialPartiallyAromaticRings, @PartiallyAromaticRingComponents); + + @FullyAromaticFusedRingSets = (); + + @PotentialFullyAromaticRings = (); + @FullyAromaticRings = (); + + @PotentialPartiallyAromaticRings = (); + @PartiallyAromaticRingComponents = (); + + ($FusedRingSetsRef, $NonFusedRingsRef) = $This->GetFusedAndNonFusedRings(); + + # Go over fused ring sets... + RINGSET: for $Index (0 .. $#{$FusedRingSetsRef}) { + $FusedRingSetRef = $FusedRingSetsRef->[$Index]; + + $SetAtomsCount = 0; + $SetAromaticAtomsCount = 0; + + for $RingAtomsRef (@{$FusedRingSetRef}) { + $SetAtomsCount += scalar @{$RingAtomsRef}; + + for $RingAtom (@{$RingAtomsRef}) { + if ($RingAtom->IsAromatic()) { + $SetAromaticAtomsCount += 1; + } + } + } + + if (!($SetAtomsCount && $SetAromaticAtomsCount)) { + next RINGSET; + } + + if ($SetAromaticAtomsCount == $SetAtomsCount) { + push @FullyAromaticFusedRingSets, $FusedRingSetRef; + } + else { + # Identify any individual rings in partial aromatic fused ring sets which might be + # fully or partially aromatic... + # + RING: for $RingAtomsRef (@{$FusedRingSetRef}) { + $RingAtomsCount = scalar @{$RingAtomsRef}; + $AromaticAtomsCount = 0; + + RINGATOM: for $RingAtom (@{$RingAtomsRef}) { + if (!$RingAtom->IsAromatic()) { + next RINGATOM; + } + $AromaticAtomsCount += 1; + } + + if (!($RingAtomsCount && $AromaticAtomsCount)) { + next RING; + } + + if ($RingAtomsCount == $AromaticAtomsCount) { + push @PotentialFullyAromaticRings, $RingAtomsRef; + } + else { + # Track partially aromatic rings in an different list before removing them for + # any overlap with other rings and then add to fully aromatic rings... + push @PotentialPartiallyAromaticRings, $RingAtomsRef; + } + } + } + } + + if (@PotentialFullyAromaticRings > 1) { + # Get any fully aromatic fused ring subsets from potentially fully aromatic rings... + my($FullyAromaticFusedRingSetsRefs, $FullyAromaticNonFusedRingsRef); + ($FullyAromaticFusedRingSetsRefs, $FullyAromaticNonFusedRingsRef) = $This->_GetFullyAromaticFusedAndNonFusedRingsInFusedSubset(\@PotentialFullyAromaticRings); + + if (@{$FullyAromaticFusedRingSetsRefs}) { + push @FullyAromaticFusedRingSets, @{$FullyAromaticFusedRingSetsRefs}; + } + if (@{$FullyAromaticNonFusedRingsRef}) { + push @FullyAromaticRings, @{$FullyAromaticNonFusedRingsRef}; + } + } + else { + push @FullyAromaticRings, @PotentialFullyAromaticRings; + } + + # Go over partial aromatic ring components... + if (@PotentialPartiallyAromaticRings) { + $PartiallyAromaticRingComponentsRef = $This->_GetPartiallyAromaticRingComponents(\@PotentialPartiallyAromaticRings, \@PotentialFullyAromaticRings); + if (@{$PartiallyAromaticRingComponentsRef}) { + push @PartiallyAromaticRingComponents, @{$PartiallyAromaticRingComponentsRef}; + } + } + + # Go over non-fused rings... + if (@{$NonFusedRingsRef}) { + ($NonFusedFullyAromaticRingsRef, $NonFusedPartiallyAromaticRingComponentsRef) = $This->_GetRingsContainingAromaticAtoms(@{$NonFusedRingsRef}); + + if (@{$NonFusedFullyAromaticRingsRef}) { + push @FullyAromaticRings, @{$NonFusedFullyAromaticRingsRef}; + } + if (@{$NonFusedPartiallyAromaticRingComponentsRef}) { + push @PartiallyAromaticRingComponents, @{$NonFusedPartiallyAromaticRingComponentsRef}; + } + } + + return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings, \@PartiallyAromaticRingComponents); +} + +# Identify fully aromatic fused sets and non-fused rings in potentially fully aromatic +# rings in fused ring sets... +# +# Fully aromatic rings in fused ring sets might contain fully aromatic fused subsets. These +# fused subets need to be tracked and treated as fused sets. +# +# Note: +# . Fused ring sets share at least one common bond, which could be used to identify +# any multiple fully aromatic fused rings sets; absence of a shared ring bond implies +# there are no fused ring sets. +# +# +sub _GetFullyAromaticFusedAndNonFusedRingsInFusedSubset { + my($This, $PotentialFullyAromaticFusedRingsRef) = @_; + my($RingIndex, $RingIndex1, $RingIndex2, $RingAtom, $RingAtomID, $RingIsFuesd, $RingIndicesGraph, $FusedRingSetIndicesRef, @RingIndices, @FusedRingPairIndices, @FusedRingSetIndicesRefs, @FullyAromaticFusedRingSets, @FullyAromaticRings, %RingIndexToAtomIDMap, %FullyAromaticFusedRingIndexMap); + + @FullyAromaticFusedRingSets = (); + @FullyAromaticRings = (); + + # Setup a ring index map for ring atoms... + # + %RingIndexToAtomIDMap = (); + for $RingIndex (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { + %{$RingIndexToAtomIDMap{$RingIndex}} = (); + for $RingAtom (@{$PotentialFullyAromaticFusedRingsRef->[$RingIndex]}) { + $RingAtomID = $RingAtom->GetID(); + $RingIndexToAtomIDMap{$RingIndex}{$RingAtomID} = $RingAtomID; + } + } + + # Identify fused ring pairs... + # + @RingIndices = (); + @FusedRingPairIndices = (); + + for $RingIndex1 (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { + push @RingIndices, $RingIndex1; + for $RingIndex2 (($RingIndex1 + 1) .. $#{$PotentialFullyAromaticFusedRingsRef}) { + $RingIsFuesd = 0; + RINGATOM: for $RingAtom (@{$PotentialFullyAromaticFusedRingsRef->[$RingIndex2]}) { + $RingAtomID = $RingAtom->GetID(); + if (exists $RingIndexToAtomIDMap{$RingIndex1}{$RingAtomID}) { + $RingIsFuesd = 1; + last RINGATOM; + } + } + if ($RingIsFuesd) { + push @FusedRingPairIndices, ($RingIndex1, $RingIndex2); + } + } + } + + if (!@FusedRingPairIndices) { + # No fused ring subset out there... + push @FullyAromaticRings, @{$PotentialFullyAromaticFusedRingsRef}; + + return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings); + } + + # Identify fused ring sets... + # + $RingIndicesGraph = new Graph(@RingIndices); + $RingIndicesGraph->AddEdges(@FusedRingPairIndices); + @FusedRingSetIndicesRefs = $RingIndicesGraph->GetConnectedComponentsVertices(); + + # Collect fully aromatic fused ring sets... + # + %FullyAromaticFusedRingIndexMap = (); + for $FusedRingSetIndicesRef (@FusedRingSetIndicesRefs) { + my(@FullyAromaticFusedRingSet) = (); + for $RingIndex (@{$FusedRingSetIndicesRef}) { + $FullyAromaticFusedRingIndexMap{$RingIndex} = $RingIndex; + push @FullyAromaticFusedRingSet, $PotentialFullyAromaticFusedRingsRef->[$RingIndex]; + } + if (@FullyAromaticFusedRingSet) { + # Sort rings by size with in the fused ring set... + @FullyAromaticFusedRingSet = sort { scalar @$a <=> scalar @$b } @FullyAromaticFusedRingSet; + push @FullyAromaticFusedRingSets, \@FullyAromaticFusedRingSet; + } + } + + # Collect fully aromatic non-fused rings... + # + RINGINDEX: for $RingIndex (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { + if (exists $FullyAromaticFusedRingIndexMap{$RingIndex}) { + next RINGINDEX; + } + push @FullyAromaticRings, $PotentialFullyAromaticFusedRingsRef->[$RingIndex]; + } + + return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings); +} + +# Identify individual non-fused rings containing aromatic atoms... +# +sub _GetIndividualRingsContainingAromaticAtoms { + my($This) = @_; + + return $This->_GetRingsContainingAromaticAtoms($This->GetRings()); +} + +# Identify individual non-fused rings containing aromatic atoms... +# +sub _GetRingsContainingAromaticAtoms { + my($This, @Rings) = @_; + my($RingAtom, $RingAtomsRef, $RingAtomsCount, $AromaticAtomsCount, $PartiallyAromaticRingComponentsRef, @FullyAromaticRings, @PartiallyAromaticRings); + + @FullyAromaticRings = (); + @PartiallyAromaticRings = (); + + RING: for $RingAtomsRef (@Rings) { + $RingAtomsCount = scalar @{$RingAtomsRef}; + $AromaticAtomsCount = 0; + + for $RingAtom (@{$RingAtomsRef}) { + if ($RingAtom->IsAromatic()) { + $AromaticAtomsCount += 1; + } + } + + if (!($AromaticAtomsCount && $RingAtomsCount)) { + next RING; + } + + if ($AromaticAtomsCount == $RingAtomsCount) { + push @FullyAromaticRings, $RingAtomsRef; + } + else { + push @PartiallyAromaticRings, $RingAtomsRef; + } + } + + $PartiallyAromaticRingComponentsRef = $This->_GetPartiallyAromaticRingComponents(\@PartiallyAromaticRings); + + return (\@FullyAromaticRings, $PartiallyAromaticRingComponentsRef); +} + +# Get connected aromatic components with in partially aromatic rings... +# +sub _GetPartiallyAromaticRingComponents { + my($This, $PotentialPartiallyAromaticRingsRef, $FullyAromaticRingsRef) = @_; + my($RingAtomsRef, $RingAtom, $RingAtomID, $Index, @PartiallyAromaticRingComponents, %FullyAromaticRingAtomsMap); + + @PartiallyAromaticRingComponents = (); + + # Setup a map for atoms involve in fully aromatic rings to remove remove partial rings + # containing only those atoms which are already part of some other fully aromatic ring + # in fused ring scenarios or some other partially aromatic ring... + # + %FullyAromaticRingAtomsMap = (); + if (defined $FullyAromaticRingsRef) { + for $RingAtomsRef (@{$FullyAromaticRingsRef}) { + for $RingAtom (@{$RingAtomsRef}) { + $RingAtomID = $RingAtom->GetID(); + $FullyAromaticRingAtomsMap{$RingAtomID} = $RingAtomID; + } + } + } + + # . Identify any connected components with in each partially aromatic ring. + # . Use ring atom indices to figure out connnected components in rings: All ring atoms + # in a connected component have sequential indices and a difference by more than + # 1 indicates a new component in the list. + # + RING: for $RingAtomsRef (@{$PotentialPartiallyAromaticRingsRef}) { + my(@AromaticRingAtoms, @AromaticRingAtomsIndices); + + @AromaticRingAtoms = (); + @AromaticRingAtomsIndices = (); + + RINGATOM: for $Index (0 .. $#{$RingAtomsRef}) { + $RingAtom = $RingAtomsRef->[$Index]; + $RingAtomID = $RingAtom->GetID(); + + if (defined $FullyAromaticRingsRef && exists $FullyAromaticRingAtomsMap{$RingAtomID}) { + next RINGATOM; + } + if (!$RingAtom->IsAromatic()) { + next RINGATOM; + } + push @AromaticRingAtoms, $RingAtom; + push @AromaticRingAtomsIndices, $Index; + + } + if (!@AromaticRingAtoms) { + next RING; + } + + # Start off with a new connected component... + # + my($ComponentNum); + $ComponentNum = scalar @PartiallyAromaticRingComponents; + @{$PartiallyAromaticRingComponents[$ComponentNum]} = (); + + $Index = 0; + push @{$PartiallyAromaticRingComponents[$ComponentNum]}, $AromaticRingAtoms[$Index]; + + for $Index (1 .. $#AromaticRingAtoms) { + if (($AromaticRingAtomsIndices[$Index] - $AromaticRingAtomsIndices[$Index -1]) > 1) { + # New connected component... + $ComponentNum += 1; + @{$PartiallyAromaticRingComponents[$ComponentNum]} = (); + } + push @{$PartiallyAromaticRingComponents[$ComponentNum]}, $AromaticRingAtoms[$Index]; + } + } + + return (\@PartiallyAromaticRingComponents); +} + +# Kekulize fully aromatic fused and individual rings along with any partially aromatic ring +# components... +# +sub _KekulizeCompleteAndPartialAromaticRings { + my($This, $AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = @_; + my($Status, $ConnectedPathsAtomsSetsRef, $ConnectedPathsBondsSetsRef, $ConnectdPathsSetsTypesRef, $PathSetIndex, $PathAtom, $AtomID, $BondID, $PathBondsRef, $DeleteAtomsAromaticity, $DeleteBondsAromaticity, %PathAtomsProcessingStatusMap, %PathBondsProcessingStatusMap); + + ($ConnectedPathsAtomsSetsRef, $ConnectedPathsBondsSetsRef, $ConnectdPathsSetsTypesRef) = $This->_SetupCompleteAndPartialAromaticRingsForKekulizaiton($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef); + + if (!@{$ConnectedPathsAtomsSetsRef}) { + # Nothing to do... + return 1; + } + + # Delete any aromaticity property set for non-ring bonds connected any two ring + # aromatic atoms... + # + $This->_ProcessNonRingAromaticBondsBetweenAromaticRingAtoms(); + + %PathAtomsProcessingStatusMap = (); + %PathBondsProcessingStatusMap = (); + + $Status = 1; + + PATHSET: for $PathSetIndex (0 .. $#{$ConnectedPathsAtomsSetsRef}) { + my($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef); + + ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef) = (undef) x 3; + + if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^FusedAromatic$/i) { + # Fused set of connected paths... + # + my($FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef); + + $FusedConnectedPathAtomsSetRef = $ConnectedPathsAtomsSetsRef->[$PathSetIndex]; + $FusedConnectedPathBondsSetRef = $ConnectedPathsBondsSetsRef->[$PathSetIndex]; + + # Prepare for kekulization... + ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef) = $This->_SetupConnectedPathSetsForKekulization($FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef); + + # Perform kekulization starting with the first path set... + $PathSetProcessingStatusRef->[0] = 'Processed'; + if (!$This->_KekulizeConnectedPathSets($FusedConnectedPathAtomsSetRef->[0], $FusedConnectedPathBondsSetRef->[0], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef, $PathSetProcessingStatusRef)) { + # Kekulization failed for the current fused paths set... + $Status = 0; + } + } + else { + # An individual connected path... + # + my(@ConnectedPathAtomsSet, @ConnectedPathBondsSet); + + @ConnectedPathAtomsSet = ($ConnectedPathsAtomsSetsRef->[$PathSetIndex]); + @ConnectedPathBondsSet = ($ConnectedPathsBondsSetsRef->[$PathSetIndex]); + + # Prepare for kekulization... + ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = $This->_SetupConnectedPathSetsForKekulization(\@ConnectedPathAtomsSet, \@ConnectedPathBondsSet); + + # Perform kekulization... + if (!$This->_KekulizeConnectedPathSets($ConnectedPathsAtomsSetsRef->[$PathSetIndex], $ConnectedPathsBondsSetsRef->[$PathSetIndex], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { + # Kekulization failed for the current path... + $Status = 0; + } + } + + # Did kekulization succeed for the current path or path set? + if (!$Status) { + last PATHSET; + } + + # Track atom and bond processing state for final assignment after kekulization + # is successfully completed for all the paths and fused path sets... + # + for $AtomID (keys %{$AtomProcessingStatusMapRef}) { + $PathAtomsProcessingStatusMap{$AtomID} = $AtomProcessingStatusMapRef->{$AtomID}; + } + + for $BondID (keys %{$BondProcessingStatusMapRef}) { + $PathBondsProcessingStatusMap{$BondID} = $BondProcessingStatusMapRef->{$BondID}; + } + } + + if (!$Status) { + carp "Warning: ${ClassName}->_KekulizeCompleteAndPartialAromaticRings: Couldn't perform kekulization for marked ring aromatic atoms..."; + return 0; + } + + # Use PathAtomsProcessingStatusMap and PathBondsProcessingStatusMap to set + # single/double bonds in the molecule after successful kekulization along with modification of + # any aromatic flags... + + for $PathSetIndex (0 .. $#{$ConnectedPathsAtomsSetsRef}) { + $DeleteAtomsAromaticity = 0; $DeleteBondsAromaticity = 0; + + if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^FusedAromatic$/i) { + for $PathBondsRef (@{$ConnectedPathsBondsSetsRef->[$PathSetIndex]}) { + $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($PathBondsRef, \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); + } + } + else { + if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^PartiallyAromatic$/i ) { + $DeleteBondsAromaticity = 1; $DeleteAtomsAromaticity = 1; + } + + if ($DeleteAtomsAromaticity) { + for $PathAtom (@{$ConnectedPathsAtomsSetsRef->[$PathSetIndex]}) { + $PathAtom->DeleteAromatic(); + } + } + + $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($ConnectedPathsBondsSetsRef->[$PathSetIndex], \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); + } + } + + return 1; +} + +# Look for any aromatic bonds outside the rings between two ring aromatic atoms +# and turn them into single non-aromatic bonds before kekulization; otherwise, kekulization +# fails. +# +# Note: +# . Two atoms marked as aromatic atoms in two different rings, such as two rings +# connected through a single bond, are still aromatic, but the bond is outside +# the ring and shouldn't be marked as aromatic. It should be set to single bond without +# any aromatic property for kekulization to succeed. +# +# For example, the molecule generated by SMILES parser for biphenyl SMILES string +# "c1ccccc1c2ccccc2" sets up an aromatic bond between the two phenyl rings, as +# it's connected to two aromatic atoms. +# +sub _ProcessNonRingAromaticBondsBetweenAromaticRingAtoms { + my($This) = @_; + my($Bond, $Atom1, $Atom2); + + BOND: for $Bond ($This->GetBonds()) { + if (!($Bond->IsAromatic() && $Bond->IsNotInRing())) { + next BOND; + } + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + if (!($Atom1->IsAromatic() && $Atom2->IsAromatic() && $Atom1->IsInRing() && $Atom2->IsInRing())) { + next BOND; + } + + $Bond->SetBondOrder(1); + $Bond->DeleteAromatic(); + } + + return $This; +} + +# Setup completelty aromatic fused and individual rings along with partially aromatic ring +# components as sets of connected paths... +# +sub _SetupCompleteAndPartialAromaticRingsForKekulizaiton { + my($This, $AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = @_; + my(@ConnectedPathsSets, @ConnectedPathsBondsSets, @ConnectdPathsSetsTypes); + + @ConnectedPathsSets = (); + @ConnectedPathsBondsSets = (); + @ConnectdPathsSetsTypes = (); + + # Setup atoms and bonds for connected paths in fused aromatic ring sets... + # + if (defined $AromaticFusedRingSetsRef && @{$AromaticFusedRingSetsRef}) { + my($RingSetIndex); + + push @ConnectdPathsSetsTypes, ('FusedAromatic') x scalar @{$AromaticFusedRingSetsRef}; + push @ConnectedPathsSets, @{$AromaticFusedRingSetsRef}; + + for $RingSetIndex (0 .. $#{$AromaticFusedRingSetsRef}) { + my(@AromaticFusedRingBondsSet); + + # Get ring bonds for each ring set... + # + @AromaticFusedRingBondsSet = $This->GetRingBondsFromRings(@{$AromaticFusedRingSetsRef->[$RingSetIndex]}); + push @ConnectedPathsBondsSets, \@AromaticFusedRingBondsSet; + } + } + + # Set up atoms and bonds for connected paths in aromatic rings... + # + if (defined $AromaticRingsRef && @{$AromaticRingsRef}) { + my(@AromaticRingBondsSets); + + push @ConnectdPathsSetsTypes, ('Aromatic') x scalar @{$AromaticRingsRef}; + push @ConnectedPathsSets, @{$AromaticRingsRef}; + + # Get ring bonds for each ring... + @AromaticRingBondsSets = $This->GetRingBondsFromRings(@{$AromaticRingsRef}); + push @ConnectedPathsBondsSets, @AromaticRingBondsSets; + } + + # Set up atoms and bonds for connected paths in partially aromatic rings... + # + if (defined $PartiallyAromaticRingComponentsRef && @{$PartiallyAromaticRingComponentsRef}) { + my($ComponentIndex); + + push @ConnectedPathsSets, @{$PartiallyAromaticRingComponentsRef}; + push @ConnectdPathsSetsTypes, ('PartiallyAromatic') x scalar @{$PartiallyAromaticRingComponentsRef}; + + for $ComponentIndex (0 .. $#{$PartiallyAromaticRingComponentsRef}) { + my(@ComponentBonds); + @ComponentBonds = $This->_GetPathBonds($This->_GetAtomsIDsFromAtoms(@{$PartiallyAromaticRingComponentsRef->[$ComponentIndex]})); + push @ConnectedPathsBondsSets, \@ComponentBonds; + } + } + + return (\@ConnectedPathsSets, \@ConnectedPathsBondsSets, \@ConnectdPathsSetsTypes); +} + +# Process non-ring connected atoms which are marked aromatic and set connected +# bonds as alternate single/double bonds... +# +# Notes: +# . Atom and bond aromaticity is deleted during kekulization of non-ring atoms. +# +sub _KekulizeAromaticAtomsNotInRings { + my($This) = @_; + my($Status, $PathIndex, $PathAtom, $PathAtomID, $PathBondID, $ConnectedPathsAtomsRef, $ConnectedPathsBondsRef, $DeleteAtomsAromaticity, $DeleteBondsAromaticity, %PathAtomsProcessingStatusMap, %PathBondsProcessingStatusMap); + + if (!$This->HasAromaticAtomsNotInRings()) { + # Nothing to do... + return 1; + } + + # Identify paths for connected components containing non-ring aromatic atoms... + ($ConnectedPathsAtomsRef, $ConnectedPathsBondsRef) = $This->_GetConnectedComponentsPathsForNonRingAromaticAtoms(); + + if (!@{$ConnectedPathsAtomsRef}) { + carp "Warning: ${ClassName}->_KekulizeAromaticAtomsNotInRings: Couldn't perform kekulization for marked non-ring aromatic atoms..."; + return 0; + } + + %PathAtomsProcessingStatusMap = (); + %PathBondsProcessingStatusMap = (); + + $Status = 1; + + PATH: for $PathIndex (0 .. $#{$ConnectedPathsAtomsRef}) { + my($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, @ConnectedPathAtomsSet, @ConnectedPathBondsSet); + + ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = (undef) x 2; + + @ConnectedPathAtomsSet = ($ConnectedPathsAtomsRef->[$PathIndex]); + @ConnectedPathBondsSet = ($ConnectedPathsBondsRef->[$PathIndex]); + + # Prepare for kekulization... + ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = $This->_SetupConnectedPathSetsForKekulization(\@ConnectedPathAtomsSet, \@ConnectedPathBondsSet); + + # Perform kekulization... + if (!$This->_KekulizeConnectedPathSets($ConnectedPathsAtomsRef->[$PathIndex], $ConnectedPathsBondsRef->[$PathIndex], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { + # Kekulization failed for the current path... + $Status = 0; + last PATH; + } + + # Track atom and bond processing state for final assignment after kekulization + # is successfully completed for all the paths and fused path sets... + # + for $PathAtomID (keys %{$AtomProcessingStatusMapRef}) { + $PathAtomsProcessingStatusMap{$PathAtomID} = $AtomProcessingStatusMapRef->{$PathAtomID}; + } + + for $PathBondID (keys %{$BondProcessingStatusMapRef}) { + $PathBondsProcessingStatusMap{$PathBondID} = $BondProcessingStatusMapRef->{$PathBondID}; + } + } + + if (!$Status) { + carp "Warning: ${ClassName}->_KekulizeAromaticAtomsNotInRings: Couldn't perform kekulization for marked non-ring aromatic atoms..."; + return 0; + } + + $DeleteAtomsAromaticity = 1; $DeleteBondsAromaticity = 1; + for $PathIndex (0 .. $#{$ConnectedPathsAtomsRef}) { + if ($DeleteAtomsAromaticity) { + for $PathAtom (@{$ConnectedPathsAtomsRef->[$PathIndex]}) { + $PathAtom->DeleteAromatic(); + } + } + $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($ConnectedPathsBondsRef->[$PathIndex], \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); + } + + return 1; +} + +# Collect path atoms for connected components paths containing non-ring aromatic atoms... +# +sub _GetConnectedComponentsPathsForNonRingAromaticAtoms { + my($This) = @_; + my($ComponentRef, $AtomIDsRef, $AtomIDsMapRef, $ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef, $ConnectedComponentsPathsAtomIDsRef, $ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef); + + # Retrieve information for marked aromatic atoms not in the rings... + ($AtomIDsRef, $AtomIDsMapRef) = $This->_GetNonRingAromaticAtomIDs(); + + # Identify connected components containing marked aromatic atoms not in the rings... + ($ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef) = $This->_GetConnectedComponentsForNonRingAromaticAtoms($AtomIDsRef); + + # Identify paths for connected components containing non-ring aromatic atoms... + ($ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef) = $This->_GetConnectedComponentsPathsAtomsAndBondsForNonRingAromaticAtoms($AtomIDsMapRef, $ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef); + + return ($ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef); +} + +# Collect information for marked aromatic atoms not in the rings... +# +sub _GetNonRingAromaticAtomIDs { + my($This) = @_; + my($Atom, $AtomID, @AtomIDs, %AtomIDsMap); + + @AtomIDs = (); + %AtomIDsMap = (); + + ATOM: for $Atom ($This->GetAtoms()) { + if (!$Atom->IsAromatic()) { + next ATOM; + } + if ($Atom->IsInRing()) { + next ATOM; + } + $AtomID = $Atom->GetID(); + + push @AtomIDs, $AtomID; + $AtomIDsMap{$AtomID} = $Atom; + } + + return (\@AtomIDs, \%AtomIDsMap); +} + +# Retrieve connected non-ring atom components as a reference to an array of references +# containing atom IDs of connecnted components... +# +sub _GetConnectedComponentsForNonRingAromaticAtoms { + my($This, $AtomIDsRef) = @_; + my($Index, $AtomID, $AtomIDsGraph, @BondedAtomPairIDs, @ComponentsAtomIDsRefs, @ComponentsAtomIDsMapRefs); + + @ComponentsAtomIDsRefs = (); + @ComponentsAtomIDsMapRefs = (); + + # Get bonded atom pair IDs... + @BondedAtomPairIDs = $This->_GetBondedAtomPairAtomIDsFromAtomIDs(@{$AtomIDsRef}); + + if (!@BondedAtomPairIDs) { + return (\@ComponentsAtomIDsRefs, \@ComponentsAtomIDsMapRefs); + } + + $AtomIDsGraph = new Graph(@{$AtomIDsRef}); + $AtomIDsGraph->AddEdges(@BondedAtomPairIDs); + + @ComponentsAtomIDsRefs = $AtomIDsGraph->GetConnectedComponentsVertices(); + + # Setup atom IDs map for each component... + for $Index (0 .. $#ComponentsAtomIDsRefs) { + %{$ComponentsAtomIDsMapRefs[$Index]} = (); + + for $AtomID (@{$ComponentsAtomIDsRefs[$Index]}) { + $ComponentsAtomIDsMapRefs[$Index]{$AtomID} = $AtomID; + } + } + + return (\@ComponentsAtomIDsRefs, \@ComponentsAtomIDsMapRefs); +} + +# Get linear paths for connected components starting and ending at terminal aromatic atoms, +# which are connected to only one other aromatic atom in the connected component.. +# +sub _GetConnectedComponentsPathsAtomsAndBondsForNonRingAromaticAtoms { + my($This, $AtomIDsMapRef, $ComponentsAtomIDsRef, $ComponentsAtomIDsMapRef) = @_; + my($Index, $AtomID, $Atom, $AtomNbr, $AtomNbrID, $NumOfNonRingAromaticNbrs, $AtomIndex1, $AtomIndex2, $AtomID1, $AtomID2, $Atom1, $Atom2, $AtomIDsGraph, $StartTerminalAtomID, $EndTerminalAtomID, @Paths, @PathAtomIDs, @PathsAtoms, @PathsBonds, @TerminalAtomIDs, @AtomIDs, @BondedAtomPairIDs); + + @PathsAtoms = (); + @PathsBonds = (); + + @TerminalAtomIDs = (); + + $Index = 0; + COMPONENT: for $Index (0 .. $#{$ComponentsAtomIDsRef}) { + @{$TerminalAtomIDs[$Index]} = (); + + # Identify terminal atoms for connected components... + # + # Notes: + # . Terminal atoms are defined as atoms connected to only one marked + # aromatic atom. + # . Linear connected compoents contain only two terminal atoms. + # + ATOM: for $AtomID (@{$ComponentsAtomIDsRef->[$Index]}) { + $Atom = $AtomIDsMapRef->{$AtomID}; + $NumOfNonRingAromaticNbrs = 0; + + ATOMNBRID: for $AtomNbr ($Atom->GetNeighbors()) { + $AtomNbrID = $AtomNbr->GetID(); + + # Is neighbor in the same connected components containing aromatic atoms? + if (!exists $ComponentsAtomIDsMapRef->[$Index]{$AtomNbrID}) { + next ATOMNBRID; + } + $NumOfNonRingAromaticNbrs++; + } + + # Is it a terminal atom? + if ($NumOfNonRingAromaticNbrs != 1) { + next ATOM; + } + push @{$TerminalAtomIDs[$Index]}, $AtomID; + } + + if (@{$TerminalAtomIDs[$Index]} != 2) { + next COMPONENT; + } + + # Setup bonded atom pair IDs for connected component... + # + @AtomIDs = @{$ComponentsAtomIDsRef->[$Index]}; + @BondedAtomPairIDs = (); + + for $AtomIndex1 ( 0 .. $#AtomIDs) { + $AtomID1 = $AtomIDs[$AtomIndex1]; + $Atom1 = $AtomIDsMapRef->{$AtomID1}; + + for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#AtomIDs) { + $AtomID2 = $AtomIDs[$AtomIndex2]; + $Atom2 = $AtomIDsMapRef->{$AtomID2}; + + if ($Atom1->IsBondedToAtom($Atom2)) { + push @BondedAtomPairIDs, ($AtomID1, $AtomID2); + } + } + } + + if (!@BondedAtomPairIDs) { + next COMPONENT; + } + + # Get path for connected component... + $AtomIDsGraph = new Graph(@AtomIDs); + $AtomIDsGraph->AddEdges(@BondedAtomPairIDs); + + ($StartTerminalAtomID, $EndTerminalAtomID) = sort { $a <=> $b } @{$TerminalAtomIDs[$Index]}; + @Paths = $AtomIDsGraph->GetPathsBetween($StartTerminalAtomID, $EndTerminalAtomID); + + if (@Paths != 1) { + next COMPONENT; + } + + @PathAtomIDs = $Paths[0]->GetVertices(); + + my(@PathAtoms); + @PathAtoms = $This->_GetAtomsFromAtomIDs(@PathAtomIDs); + push @PathsAtoms, \@PathAtoms; + + my(@PathBonds); + @PathBonds = $This->_GetPathBonds(@PathAtomIDs); + push @PathsBonds, \@PathBonds; + + } + + return (\@PathsAtoms, \@PathsBonds); +} + +# Setup initial processing status of atoms and bonds involved in connected paths +# before starting kekulization... +# +# Possible atom processing status: DoubleBondPossible, DoubleBondAssigned, DoubleBondNotPossible +# Initial status: DoubleBondPossible or DoubleBondNotPossible +# +# Possible bond processing status: DoubleBondAssigned, SingleBondAssigned, NotProcessed +# +# Possible paths processing status: Processed, NotProcessed +# Initial status: NotProcessed +# +sub _SetupConnectedPathSetsForKekulization { + my($This, $PathAtomsSetsRef, $PathBondsSetsRef) = @_; + my($PathIndex, $PathAtomsRef, $PathBondsRef, $Atom, $AtomID, $Bond, $BondID, %AtomProcessingStatusMap, %BondProcessingStatusMap, @PathsProcessingStatus, %InitialPathBondOrderMap); + + # Possible path set status values: Processed, NotProcessed + # Initial value: NotProcessed + # + @PathsProcessingStatus = ('NotProcessed') x scalar @{$PathAtomsSetsRef}; + + # Collect initial bond order of path bonds before setting bond orders to 1 + # and use it to set the bond order back to intial value after it has been processed for + # availability of double bonds... + # + %InitialPathBondOrderMap = (); + for $PathBondsRef (@{$PathBondsSetsRef}) { + BOND: for $Bond (@{$PathBondsRef}) { + $BondID = $Bond->GetID(); + if (exists $InitialPathBondOrderMap{$BondID}) { + next BOND; + } + $InitialPathBondOrderMap{$BondID} = $Bond->GetBondOrder(); + $Bond->SetBondOrder(1); + } + } + + %AtomProcessingStatusMap = (); + %BondProcessingStatusMap = (); + + for $PathIndex (0 .. $#{$PathAtomsSetsRef}) { + + $PathAtomsRef = $PathAtomsSetsRef->[$PathIndex]; + ATOM: for $Atom (@{$PathAtomsRef}) { + $AtomID = $Atom->GetID(); + if (exists $AtomProcessingStatusMap{$AtomID}) { + next ATOM; + } + $AtomProcessingStatusMap{$AtomID} = ($Atom->GetNumOfBondsAvailableForNonHydrogenAtoms() >= 1) ? 'DoubleBondPossible' : 'DoubleBondNotPossible'; + } + + $PathBondsRef = $PathBondsSetsRef->[$PathIndex]; + BOND: for $Bond (@{$PathBondsRef}) { + $BondID = $Bond->GetID(); + if (exists $BondProcessingStatusMap{$BondID}) { + next BOND; + } + $BondProcessingStatusMap{$BondID} = 'NotProcessed'; + } + } + + # Set bond orders back to initial bond orders... + for $PathIndex (0 .. $#{$PathAtomsSetsRef}) { + $PathBondsRef = $PathBondsSetsRef->[$PathIndex]; + + for $Bond (@{$PathBondsRef}) { + $BondID = $Bond->GetID(); + if (exists $InitialPathBondOrderMap{$BondID}) { + $Bond->SetBondOrder($InitialPathBondOrderMap{$BondID}); + } + } + } + + return (\%AtomProcessingStatusMap, \%BondProcessingStatusMap, \@PathsProcessingStatus); +} + +# Kekulize connected path sets corresponding to fused rings, individual rings, or any other +# connected path... +# +# Note: +# . PathAtomsRef and PathBondsRef contain paths and bonds corresponding to path +# under consideration for kekulization +# . PathAtomsSetsRef and PathBondsSetsRef contain any other available paths fused +# to the path being kekulized +# . _KekulizeConnectedPathSets is invoked recursively to kekulize all available paths +# +sub _KekulizeConnectedPathSets { + my($This, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef) = @_; + my($PathBond); + + # Get next available path bond... + $PathBond = $This->_GetNextAvailablePathBondForKekulization($PathBondsRef, $BondProcessingStatusMapRef); + + if ($PathBond) { + return $This->_ProcessNextAvailablePathBondForKekulization($PathBond, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef); + } + + # Did kekulization succeed for the current path bonds? + if (!$This->_DidKekulizationSucceedForPathBonds($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { + return 0; + } + + # Is there any other path available for kekulization? + ($PathAtomsRef, $PathBondsRef) = $This->_GetNextAvailablePathForKekulization($PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef); + + if ($PathAtomsRef && $PathBondsRef) { + # Recursively call itself to kekulize next path, which could either be a new path or part + # of a fused paths corresponding to fused ring sets... + # + return $This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef); + } + + return 1; +} + +# Get next available path bond in a list of path bonds... +# +sub _GetNextAvailablePathBondForKekulization { + my($This, $PathBondsRef, $BondProcessingStatusMapRef) = @_; + my($AvailablePathBond, $PathBond, $PathBondID); + + $AvailablePathBond = undef; + + BOND: for $PathBond (@{$PathBondsRef}) { + $PathBondID = $PathBond->GetID(); + if (!exists $BondProcessingStatusMapRef->{$PathBondID}) { + next BOND; + } + if ($BondProcessingStatusMapRef->{$PathBondID} =~ /^NotProcessed$/i) { + $AvailablePathBond = $PathBond; + last BOND; + } + } + + return ($AvailablePathBond); +} + +# Process next available path bond for kekulizaiton... +# +sub _ProcessNextAvailablePathBondForKekulization { + my($This, $PathBond, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef) = @_; + my($PathBondID, $PathAtom1, $PathAtom2, $PathAtomID1, $PathAtomID2, %CurrentAtomProcessingStatusMap, %CurrentBondProcessingStatusMap); + + $PathBondID = $PathBond->GetID(); + + ($PathAtom1, $PathAtom2) = $PathBond->GetAtoms(); + ($PathAtomID1, $PathAtomID2) = ($PathAtom1->GetID(), $PathAtom2->GetID()); + + %CurrentAtomProcessingStatusMap = %{$AtomProcessingStatusMapRef}; + %CurrentBondProcessingStatusMap = %{$BondProcessingStatusMapRef}; + + # Is it possible to assign a double bond to the current path bond? + if ($AtomProcessingStatusMapRef->{$PathAtomID1} =~ /^DoubleBondPossible$/i && $AtomProcessingStatusMapRef->{$PathAtomID2} =~ /^DoubleBondPossible$/i ) { + # Set current bond to double bond by appropriately marking atom and bond process status... + $AtomProcessingStatusMapRef->{$PathAtomID1} = 'DoubleBondAssigned'; + $AtomProcessingStatusMapRef->{$PathAtomID2} = 'DoubleBondAssigned'; + + $BondProcessingStatusMapRef->{$PathBondID} = 'DoubleBondAssigned'; + + # Recursively call _KekulizeConnectedPathSets to kekulize next available bond... + if ($This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef)) { + return 1; + } + + # Double bond at the current ring bond position didn't lead to successful kekulization... + %{$AtomProcessingStatusMapRef} = %CurrentAtomProcessingStatusMap; + %{$BondProcessingStatusMapRef} = %CurrentBondProcessingStatusMap; + } + + # Try single bond at the current ring bond position and recursively call _KekulizeConnectedPathSets to kekulize + # rest of the ring bonds... + # + $BondProcessingStatusMapRef->{$PathBondID} = 'SingleBondAssigned'; + + if ($This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef)) { + return 1; + } + + %{$AtomProcessingStatusMapRef} = %CurrentAtomProcessingStatusMap; + %{$BondProcessingStatusMapRef} = %CurrentBondProcessingStatusMap; + + # Kekulization didn't work out for path bonds... + + return 0; + +} + +# Get next available path for kekulization from a set of fused ring paths... +# +sub _GetNextAvailablePathForKekulization { + my($This, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = @_; + my($PathIndex, $AvailablePathIndex, $PathAtomsRef, $PathBondsRef, $PathBond, $PathBondID, $MaxNumOfPathBondsProcessed, $NumOfPathBondsProcessed); + + ($PathAtomsRef, $PathBondsRef, $AvailablePathIndex) = (undef) x 3; + + if (!(defined($PathAtomsSetsRef) && defined($PathBondsSetsRef) && defined($PathsProcessingStatusRef))) { + return ($PathAtomsRef, $PathBondsRef); + } + + $MaxNumOfPathBondsProcessed = -999; + $AvailablePathIndex = undef; + + PATHINDEX: for $PathIndex (0 .. $#{$PathsProcessingStatusRef}) { + if ($PathsProcessingStatusRef->[$PathIndex] =~ /^Processed$/i) { + next PATHINDEX; + } + + # Count of already processed bonds in an unprocessed path bonds through + # their participation in any fused bonds sets... + # + $NumOfPathBondsProcessed = 0; + PATHBOND: for $PathBond (@{$PathBondsSetsRef->[$PathIndex]}) { + $PathBondID = $PathBond->GetID(); + if ($BondProcessingStatusMapRef->{$PathBondID} =~ /^NotProcessed$/i) { + next PATHBOND; + } + $NumOfPathBondsProcessed++; + } + + if ($NumOfPathBondsProcessed > $MaxNumOfPathBondsProcessed) { + $AvailablePathIndex = $PathIndex; + $MaxNumOfPathBondsProcessed = $NumOfPathBondsProcessed; + } + + } + + # Is any path available? + if (!$AvailablePathIndex) { + return ($PathAtomsRef, $PathBondsRef); + } + + $PathsProcessingStatusRef->[$AvailablePathIndex] = 'Processed'; + + $PathAtomsRef = $PathAtomsSetsRef->[$AvailablePathIndex]; + $PathBondsRef = $PathBondsSetsRef->[$AvailablePathIndex]; + + return ($PathAtomsRef, $PathBondsRef); +} + +# Check for kekulization in a specific set of path bonds. For successful kekulization, all +# all path atoms marked with DoubleBondPossible must be involved in a path double bond... +# +sub _DidKekulizationSucceedForPathBonds { + my($This, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = @_; + my($PathAtom, $PathAtomID); + + for $PathAtom (@{$PathAtomsRef}) { + $PathAtomID = $PathAtom->GetID(); + if (exists $AtomProcessingStatusMapRef->{$PathAtomID} && $AtomProcessingStatusMapRef->{$PathAtomID} =~ /^DoubleBondPossible$/i) { + return 0; + } + } + return 1; +} + +# Assign bond orders to the bonds in a molecule which have been successfully +# kekulized along with optional clearing of aromaticty property... +# +sub _ProcessBondOrdersAssignedDuringSuccessfulKekulization { + my($This, $BondsRef, $BondsProcessingStatusMapRef, $DeleteBondsAromaticity) = @_; + my($Bond, $BondID, $BondOrder); + + $DeleteBondsAromaticity = defined $DeleteBondsAromaticity ? $DeleteBondsAromaticity : 0; + + BOND: for $Bond (@{$BondsRef}) { + $BondID = $Bond->GetID(); + + if (!exists $BondsProcessingStatusMapRef->{$BondID}) { + carp "Warning: ${ClassName}->_ProcessBondOrdersAssignedDuringSuccessfulKekulization: Couldn't process bond with bond ID, $BondID: It's not available in the list of bonds processed for kekulization..."; + next BOND; + } + + $BondOrder = ($BondsProcessingStatusMapRef->{$BondID} =~ /^DoubleBondAssigned$/i) ? 2 : 1; + $Bond->SetBondOrder($BondOrder); + + if ($DeleteBondsAromaticity) { + $Bond->DeleteAromatic(); + } + } + return $This; +} + +# Does molecule contains aromatic rings? +# +sub HasAromaticRings { + my($This) = @_; + + return $This->GetNumOfAromaticRings() ? 1 : 0; +} + +# Does molecule contains any aromatic atom in a ring? +# +sub HasAromaticAtomsInRings { + my($This) = @_; + my($Atom); + + ATOM: for $Atom ($This->GetAtoms()) { + if (!$Atom->IsAromatic()) { + next ATOM; + } + if ($Atom->IsInRing()) { + return 1; + } + } + return 0; +} + +# Does molecule contains any aromatic atom not in a ring? +# +sub HasAromaticAtomsNotInRings { + my($This) = @_; + my($Atom); + + ATOM: for $Atom ($This->GetAtoms()) { + if (!$Atom->IsAromatic()) { + next ATOM; + } + if ($Atom->IsNotInRing()) { + return 1; + } + } + return 0; +} + +# Does molecule contains rings? +# +sub HasRings { + my($This) = @_; + + return $This->IsCyclic(); +} + +# Does molecule contains only one ring? +# +sub HasOnlyOneRing { + my($This) = @_; + + return $This->IsUnicyclic(); +} + +# Does molecule contains any rings? +# +sub HasNoRings { + my($This) = @_; + + return $This->IsAcyclic(); +} + +# Get size of smallest ring... +# +sub GetSizeOfSmallestRing { + my($This) = @_; + + return $This->GetSizeOfSmallestCycle(); +} + +# Get size of largest ring... +# +sub GetSizeOfLargestRing { + my($This) = @_; + + return $This->GetSizeOfLargestCycle(); +} + +# Get number of rings... +# +sub GetNumOfRings { + my($This) = @_; + + return $This->GetNumOfCycles(); +} + +# Get number of aromatic rings... +# +sub GetNumOfAromaticRings { + my($This) = @_; + my($NumOfRings); + + $NumOfRings = scalar $This->GetAromaticRings(); + + return $NumOfRings; +} + +# Get num of rings with odd size... +# +sub GetNumOfRingsWithOddSize { + my($This) = @_; + + return $This->GetNumOfCyclesWithOddSize(); +} + +# Get num of rings with even size... +# +sub GetNumOfRingsWithEvenSize { + my($This) = @_; + + return $This->GetNumOfCyclesWithEvenSize(); +} + +# Get num of rings with specified size... +# +sub GetNumOfRingsWithSize { + my($This, $RingSize) = @_; + + return $This->GetNumOfCyclesWithSize($RingSize); +} + +# Get num of rings with size less than a specified size... +# +sub GetNumOfRingsWithSizeLessThan { + my($This, $RingSize) = @_; + + return $This->GetNumOfCyclesWithSizeLessThan($RingSize); +} + +# Get num of rings with size greater than a specified size... +# +sub GetNumOfRingsWithSizeGreaterThan { + my($This, $RingSize) = @_; + + return $This->GetNumOfCyclesWithSizeGreaterThan($RingSize); +} + +# Get largest ring as an array containing ring atoms... +# +sub GetLargestRing { + my($This) = @_; + + return $This->_GetRing($This->GetLargestCycle()); +} + +# Get smallest ring as an array containing ring atoms... +# +sub GetSmallestRing { + my($This) = @_; + + return $This->_GetRing($This->GetSmallestCycle()); +} + +# Get rings as an array containing references to arrays with ring atoms... +# +sub GetRings { + my($This) = @_; + + return $This->_GetRings($This->GetCycles()); +} + +# Get aromatic rings as an array containing references to arrays with ring atoms... +# +sub GetAromaticRings { + my($This) = @_; + + return $This->_GetAromaticRings($This->GetCycles()); +} + +# Get odd size rings as an array containing references to arrays with ring atoms... +# +sub GetRingsWithOddSize { + my($This) = @_; + + return $This->_GetRings($This->GetCyclesWithOddSize()); +} + +# Get even size rings as an array containing references to arrays with ring atoms... +# +sub GetRingsWithEvenSize { + my($This) = @_; + + return $This->_GetRings($This->GetCyclesWithEvenSize()); +} + +# Get rings with a specific size as an array containing references to arrays with ring atoms... +# +sub GetRingsWithSize { + my($This, $RingSize) = @_; + + return $This->_GetRings($This->GetCyclesWithSize($RingSize)); +} + +# Get rings with size less than a specific size as an array containing references to arrays with ring atoms... +# +sub GetRingsWithSizeLessThan { + my($This, $RingSize) = @_; + + return $This->_GetRings($This->GetCyclesWithSizeLessThan($RingSize)); +} + +# Get rings with size greater than a specific size as an array containing references to arrays with ring atoms... +# +sub GetRingsWithSizeGreaterThan { + my($This, $RingSize) = @_; + + return $This->_GetRings($This->GetCyclesWithSizeGreaterThan($RingSize)); +} + +# Generate an array of bond objects for an array of ring atoms and return an array +# of bond objects... +# +sub GetRingBonds { + my($This, @RingAtoms) = @_; + my(@Bonds); + + @Bonds = (); + if (!@RingAtoms) { + # Return an empty ring bonds list... + return @Bonds; + } + + my(@RingAtomIDs); + + @RingAtomIDs = (); + @RingAtomIDs = $This->_GetAtomsIDsFromAtoms(@RingAtoms); + if (!@RingAtomIDs) { + carp "Warning: ${ClassName}->GetRingBonds: No ring bonds retrieved: Atom IDs couldn't be retrieved for specified atoms..."; + return @Bonds; + } + + # Add start atom to the end to make it a cyclic path for ring: It's taken out during conversion + # of cyclic path to a ring... + push @RingAtomIDs, $RingAtomIDs[0]; + + return $This->_GetPathBonds(@RingAtomIDs); +} + +# Generate an array containing references to arrays of ring bond objects for rings specified +# in an array of references to ring atoms... +# +sub GetRingBondsFromRings { + my($This, @RingAtomsSets) = @_; + my($RingAtomsRef, @RingBondsSets); + + @RingBondsSets = (); + for $RingAtomsRef (@RingAtomsSets) { + my(@RingBonds); + @RingBonds = $This->GetRingBonds(@{$RingAtomsRef}); + + push @RingBondsSets, \@RingBonds; + } + + return @RingBondsSets; +} + +# Does molecule has any fused rings? +# +sub HasFusedRings { + my($This) = @_; + + return $This->HasFusedCycles(); +} + +# Get references to array of fused ring sets and non-fused rings. Fused ring sets array reference +# contains refernces to arrays of rings; Non-fused rings array reference contains references to +# arrays of ring atoms... +# rings. +# +sub GetFusedAndNonFusedRings { + my($This) = @_; + my($FusedCyclesSetsRef, $NonFusedCyclesRef, @FusedRingSets, @NonFusedRings); + + @FusedRingSets = (); @NonFusedRings = (); + ($FusedCyclesSetsRef, $NonFusedCyclesRef) = $This->GetFusedAndNonFusedCycles(); + if (!(defined($FusedCyclesSetsRef) && defined($NonFusedCyclesRef))) { + return (\@FusedRingSets, \@NonFusedRings); + } + my($FusedCyclesSetRef); + + for $FusedCyclesSetRef (@{$FusedCyclesSetsRef}) { + my(@FusedRingSet); + @FusedRingSet = (); + @FusedRingSet = $This->_GetRings(@{$FusedCyclesSetRef}); + push @FusedRingSets, \@FusedRingSet; + } + + @NonFusedRings = $This->_GetRings(@{$NonFusedCyclesRef}); + + return (\@FusedRingSets, \@NonFusedRings); +} + +# Get rings as an array containing references to arrays with ring atoms... +# +sub _GetRings { + my($This, @CyclicPaths) = @_; + my($CyclicPath, @Rings); + + @Rings = (); + if (!@CyclicPaths) { + return @Rings; + } + if (!@CyclicPaths) { + # Return an empty ring list... + return @Rings; + } + + for $CyclicPath (@CyclicPaths) { + my(@RingAtoms); + @RingAtoms = (); + push @RingAtoms, $This->_GetRing($CyclicPath); + + push @Rings, \@RingAtoms; + } + return @Rings; +} + +# Get aromatic rings as an array containing references to arrays with ring atoms... +# +sub _GetAromaticRings { + my($This, @CyclicPaths) = @_; + my($RingAtomsRef, @Rings, @AromaticRings); + + @AromaticRings = (); + @Rings = $This->_GetRings(@CyclicPaths); + + if (!@Rings) { + return @AromaticRings; + } + RING: for $RingAtomsRef (@Rings) { + if (!$This->IsRingAromatic(@{$RingAtomsRef})) { + next RING; + } + my(@RingAtoms); + @RingAtoms = (); + push @RingAtoms, @{$RingAtomsRef}; + + push @AromaticRings, \@RingAtoms; + } + return @AromaticRings; +} + +# Map atom IDs in cyclic path to atoms and return a reference to an array containing ring atoms... +# +# Note: +# . Start and end vertex is same for cyclic paths. So end atom is removed before +# returning atoms array as ring atoms... +# +sub _GetRing { + my($This, $CyclicPath) = @_; + my(@RingAtoms); + + @RingAtoms = (); + if (!defined $CyclicPath) { + # Return an empty atoms list... + return @RingAtoms; + } + + @RingAtoms = $This->_GetPathAtoms($CyclicPath); + if (@RingAtoms) { + pop @RingAtoms; + } + return @RingAtoms; +} + +# Map atom IDs to atoms and return a reference to an array containing these atoms... +# +sub _GetPathAtoms { + my($This, $Path) = @_; + my(@PathAtoms); + + @PathAtoms = (); + if (!defined $Path) { + carp "Warning: ${ClassName}->_GetPathAtoms: No path atoms retrieved: Path must be defined..."; + return @PathAtoms; + } + my(@AtomIDs); + + @AtomIDs = (); + @AtomIDs = $Path->GetVertices(); + + @PathAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); + + return @PathAtoms; +} + +# Get bonds for a path specified by atom IDs... +# +sub _GetPathBonds { + my($This, @AtomIDs) = @_; + my($Index, $AtomID1, $AtomID2, @Bonds, @EdgesAtomIDs); + + @Bonds = (); @EdgesAtomIDs = (); + + if (!@AtomIDs || @AtomIDs == 1) { + return @Bonds; + } + + # Setup edges... + for $Index (0 .. ($#AtomIDs - 1) ) { + $AtomID1 = $AtomIDs[$Index]; + $AtomID2 = $AtomIDs[$Index + 1]; + push @EdgesAtomIDs, ($AtomID1, $AtomID2); + } + @Bonds = $This->GetEdgesProperty('Bond', @EdgesAtomIDs); + + return @Bonds; +} + +# Map atom ID to an atom... +# +sub _GetAtomFromAtomID { + my($This, $AtomID) = @_; + + return $This->GetVertexProperty('Atom', $AtomID); +} + +# Map atom IDs to atoms and return an array containing these atoms... +# +sub _GetAtomsFromAtomIDs { + my($This, @AtomIDs) = @_; + + return $This->GetVerticesProperty('Atom', @AtomIDs); +} + +# Map atoms to atom IDs and return an array containing these atoms... +# +sub _GetAtomsIDsFromAtoms { + my($This, @Atoms) = @_; + + return map { $_->GetID() } @Atoms; +} + +# Get bonded atom pair atom IDs for specified list of atom IDs... +# +sub _GetBondedAtomPairAtomIDsFromAtomIDs { + my($This, @AtomIDs) = @_; + my($AtomIndex1, $AtomID1, $Atom1, $AtomIndex2, $AtomID2, $Atom2, @Atoms, @BondedAtomPairIDs); + + @BondedAtomPairIDs = (); + @Atoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); + + for $AtomIndex1 ( 0 .. $#Atoms) { + $Atom1 = $Atoms[$AtomIndex1]; + $AtomID1 = $Atom1->GetID(); + + ATOMINDEX2: for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#Atoms) { + $Atom2 = $Atoms[$AtomIndex2]; + if (!$Atom1->IsBondedToAtom($Atom2)) { + next ATOMINDEX2; + } + $AtomID2 = $Atom2->GetID(); + + push @BondedAtomPairIDs, ($AtomID1, $AtomID2); + } + } + + return @BondedAtomPairIDs; +} + +# Get bonded atom pair atoms for specified list of atoms... +# +sub _GetBondedAtomPairAtomsFromAtoms { + my($This, @Atoms) = @_; + my($AtomIndex1, $Atom1, $AtomIndex2, $Atom2, @BondedAtomPairAtoms); + + @BondedAtomPairAtoms = (); + + for $AtomIndex1 ( 0 .. $#Atoms) { + $Atom1 = $Atoms[$AtomIndex1]; + + ATOMINDEX2: for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#Atoms) { + $Atom2 = $Atoms[$AtomIndex2]; + if ($Atom1->IsBondedToAtom($Atom2)) { + next ATOMINDEX2; + } + + push @BondedAtomPairAtoms, ($Atom1, $Atom2); + } + } + + return @BondedAtomPairAtoms; +} + +# Is atom in a ring? +# +sub _IsAtomInRing { + my($This, $Atom) = @_; + + return $This->IsCyclicVertex($Atom->GetID()); +} + +# Is atom not in a ring? +# +sub _IsAtomNotInRing { + my($This, $Atom) = @_; + + return $This->IsAcyclicVertex($Atom->GetID()); +} + +# Is atom only in one ring? +# +sub _IsAtomInOnlyOneRing { + my($This, $Atom) = @_; + + return $This->IsUnicyclicVertex($Atom->GetID()); +} + +# Is atom in a ring of specified size? +# +sub _IsAtomInRingOfSize { + my($This, $Atom, $RingSize) = @_; + + return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize) ? 1 : 0; +} + +# Get size of smallest ring containing specified atom... +# +sub _GetSizeOfSmallestAtomRing { + my($This, $Atom) = @_; + + return $This->GetSizeOfSmallestVertexCycle($Atom->GetID()); +} + +# Get size of largest ring containing specified atom... +# +sub _GetSizeOfLargestAtomRing { + my($This, $Atom) = @_; + + return $This->GetSizeOfLargestVertexCycle($Atom->GetID()); +} + +# Get number of rings containing specified atom... +# +sub _GetNumOfAtomRings { + my($This, $Atom) = @_; + + return $This->GetNumOfVertexCycles($Atom->GetID()); +} + +# Get number of rings with odd size containing specified atom... +# +sub _GetNumOfAtomRingsWithOddSize { + my($This, $Atom) = @_; + + return $This->GetNumOfVertexCyclesWithOddSize($Atom->GetID()); +} + +# Get number of rings with even size containing specified atom... +# +sub _GetNumOfAtomRingsWithEvenSize { + my($This, $Atom) = @_; + + return $This->GetNumOfVertexCyclesWithEvenSize($Atom->GetID()); +} + +# Get number of rings with specified size containing specified atom... +# +sub _GetNumOfAtomRingsWithSize { + my($This, $Atom, $RingSize) = @_; + + return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize); +} + +# Get number of rings with size less than specified containing specified atom... +# +sub _GetNumOfAtomRingsWithSizeLessThan { + my($This, $Atom, $RingSize) = @_; + + return $This->GetNumOfVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize); +} + +# Get number of rings with size greater than specified containing specified atom... +# +sub _GetNumOfAtomRingsWithSizeGreaterThan { + my($This, $Atom, $RingSize) = @_; + + return $This->GetNumOfVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize); +} + +# Get smallest ring as an array containing ring atoms... +# +sub _GetSmallestAtomRing { + my($This, $Atom) = @_; + + return $This->_GetRing($This->GetSmallestVertexCycle($Atom->GetID())); +} + +# Get odd size rings an array of references to arrays containing ring atoms... +# +sub _GetLargestAtomRing { + my($This, $Atom) = @_; + + return $This->_GetRing($This->GetLargestVertexCycle($Atom->GetID())); +} + +# Get all rings an array of references to arrays containing ring atoms... +# +sub _GetAtomRings { + my($This, $Atom) = @_; + + return $This->_GetRings($This->GetVertexCycles($Atom->GetID())); +} + +# Get odd size rings an array of references to arrays containing ring atoms... +# +sub _GetAtomRingsWithOddSize { + my($This, $Atom) = @_; + + return $This->_GetRings($This->GetVertexCyclesWithOddSize($Atom->GetID())); +} + +# Get even size rings an array of references to arrays containing ring atoms... +# +sub _GetAtomRingsWithEvenSize { + my($This, $Atom) = @_; + + return $This->_GetRings($This->GetVertexCyclesWithEvenSize($Atom->GetID())); +} + +# Get rings with specified size an array of references to arrays containing ring atoms... +# +sub _GetAtomRingsWithSize { + my($This, $Atom, $RingSize) = @_; + + return $This->_GetRings($This->GetVertexCyclesWithSize($Atom->GetID(), $RingSize)); +} + +# Get rings with size less than specfied size as an array of references to arrays containing ring atoms... +# +sub _GetAtomRingsWithSizeLessThan { + my($This, $Atom, $RingSize) = @_; + + return $This->_GetRings($This->GetVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize)); +} + +# Get rings with size less than specfied size as an array of references to arrays containing ring atoms... +# +sub _GetAtomRingsWithSizeGreaterThan { + my($This, $Atom, $RingSize) = @_; + + return $This->_GetRings($This->GetVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize)); +} + +# Is bond in a ring? +# +sub _IsBondInRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->IsCyclicEdge($Atom1->GetID(), $Atom2->GetID()); +} + +# Is bond not in a ring? +# +sub _IsBondNotInRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->IsAcyclicEdge($Atom1->GetID(), $Atom2->GetID()); +} + +# Is bond only in one ring? +# +sub _IsBondInOnlyOneRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->IsUnicyclicEdge($Atom1->GetID(), $Atom2->GetID()); +} + +# Is bond in a ring of specified size? +# +sub _IsBondInRingOfSize { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize) ? 1 : 0; +} + +# Get size of smallest ring containing specified bond... +# +sub _GetSizeOfSmallestBondRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetSizeOfSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID()); +} + +# Get size of largest ring containing specified bond... +# +sub _GetSizeOfLargestBondRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetSizeOfLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID()); +} + +# Get number of rings containing specified bond... +# +sub _GetNumOfBondRings { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCycles($Atom1->GetID(), $Atom2->GetID()); +} + +# Get number of rings with odd size containing specified bond... +# +sub _GetNumOfBondRingsWithOddSize { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID()); +} + +# Get number of rings with even size containing specified bond... +# +sub _GetNumOfBondRingsWithEvenSize { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID()); +} + +# Get number of rings with specified size containing specified bond... +# +sub _GetNumOfBondRingsWithSize { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize); +} + +# Get number of rings with size less than specified containing specified bond... +# +sub _GetNumOfBondRingsWithSizeLessThan { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize); +} + +# Get number of rings with size greater than specified containing specified bond... +# +sub _GetNumOfBondRingsWithSizeGreaterThan { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->GetNumOfEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize); +} + +# Get smallest ring as an array containing ring atoms... +# +sub _GetSmallestBondRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRing($This->GetSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID())); +} + +# Get odd size rings an array of references to arrays containing ring atoms... +# +sub _GetLargestBondRing { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRing($This->GetLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID())); +} + +# Get all rings an array of references to arrays containing ring atoms... +# +sub _GetBondRings { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCycles($Atom1->GetID(), $Atom2->GetID())); +} + +# Get odd size rings an array of references to arrays containing ring atoms... +# +sub _GetBondRingsWithOddSize { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID())); +} + +# Get even size rings an array of references to arrays containing ring atoms... +# +sub _GetBondRingsWithEvenSize { + my($This, $Bond) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID())); +} + +# Get rings with specified size an array of references to arrays containing ring atoms... +# +sub _GetBondRingsWithSize { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize)); +} + +# Get rings with size less than specfied size as an array of references to arrays containing ring atoms... +# +sub _GetBondRingsWithSizeLessThan { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize)); +} + +# Get rings with size less than specfied size as an array of references to arrays containing ring atoms... +# +sub _GetBondRingsWithSizeGreaterThan { + my($This, $Bond, $RingSize) = @_; + my($Atom1, $Atom2); + + ($Atom1, $Atom2) = $Bond->GetAtoms(); + + return $This->_GetRings($This->GetEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize)); +} + + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with length +# upto a specified length and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Note: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPathsStartingAtWithLengthUpto method. +# +sub GetAllAtomPathsStartingAtWithLengthUpto { + my($This, $StartAtom, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles); +} + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with +# specified length and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Note: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPathsStartingAtWithLengthUpto method. +# +sub GetAllAtomPathsStartingAtWithLength { + my($This, $StartAtom, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLength', $StartAtom, $Length, $AllowCycles); +} + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with all +# possible lengths and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Note: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPathsStartingAt method. +# +sub GetAllAtomPathsStartingAt { + my($This, $StartAtom, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AllAtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles); +} + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with length +# upto a specified length and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +sub GetAtomPathsStartingAtWithLengthUpto { + my($This, $StartAtom, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles); +} + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with +# specified length and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +sub GetAtomPathsStartingAtWithLength { + my($This, $StartAtom, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AtomPathsWithLength', $StartAtom, $Length, $AllowCycles); +} + +# Get atom paths starting from a specified atom as a reference to an array containing references +# to arrays with path atoms. +# +# Path atoms atoms correspond to to all possible paths for specified atom in molecule with all +# possible lengths and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# +sub GetAtomPathsStartingAt { + my($This, $StartAtom, $AllowCycles) = @_; + + return $This->_GetAtomPathsStartingAt('AtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles); +} + +# Get atom paths as an array containing references to arrays with path atoms... +# +sub _GetAtomPathsStartingAt { + my($This, $Mode, $StartAtom, $Length, $AllowCycles) = @_; + my(@AtomPaths); + + @AtomPaths = (); + if (!defined $StartAtom) { + carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom is not defined..."; + return @AtomPaths; + } + if (!$This->HasAtom($StartAtom)) { + carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom doesn't exist..."; + return @AtomPaths; + } + my($StartAtomID, @Paths); + + $StartAtomID = $StartAtom->GetID(); + @Paths = (); + + # Collect appropriate atom paths... + MODE: { + if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { @Paths = $This->GetPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AtomPathsWithLength$/i) { @Paths = $This->GetPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AtomPathsWithAllLengths$/i) { @Paths = $This->GetPathsStartingAt($StartAtomID, $AllowCycles); last MODE; } + + if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { @Paths = $This->GetAllPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AllAtomPathsWithLength$/i) { @Paths = $This->GetAllPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { @Paths = $This->GetAllPathsStartingAt($StartAtomID, $AllowCycles); last MODE; } + + print "Warn: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Mode, $Mode, is not supported..."; + return @AtomPaths; + } + return $This->_GetAtomPathsFromPaths(\@Paths); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with length +# upto a specified length and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Notes: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPathsWithLengthUpto method. +# +sub GetAllAtomPathsWithLengthUpto { + my($This, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AllAtomPathsWithLengthUpto', $Length, $AllowCycles); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with +# a specified length and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Notes: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPathsWithLengthUpto method. +# +sub GetAllAtomPathsWithLength { + my($This, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AllAtomPathsWithLength', $Length, $AllowCycles); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with all +# possible lengths and sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +# Notes: +# . For molecule without any rings, this method returns the same set of atom paths +# as GetAtomPaths method. +# +sub GetAllAtomPaths { + my($This, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AllAtomPathsWithAllLengths', undef, $AllowCycles); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with length +# upto a specified length and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +sub GetAtomPathsWithLengthUpto { + my($This, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AtomPathsWithLengthUpto', $Length, $AllowCycles); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with +# a specified length and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +sub GetAtomPathsWithLength { + my($This, $Length, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AtomPathsWithLength', $Length, $AllowCycles); +} + + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +# Path atoms correspond to to all possible paths for each atom in molecule with all +# possible lengths and no sharing of bonds in paths traversed. By default, rings are +# included in paths. A path containing a ring is terminated at an atom completing the ring. +# +sub GetAtomPaths { + my($This, $AllowCycles) = @_; + + return $This->_GetAtomPaths('AtomPathsWithAllLengths', undef, $AllowCycles); +} + +# Get atom paths for all atoms as a reference to an array containing references to arrays with +# path atoms. +# +sub _GetAtomPaths { + my($This, $Mode, $Length, $AllowCycles) = @_; + my($PathsRef, @AtomPaths); + + @AtomPaths = (); + # Collect appropriate atom paths... + MODE: { + if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetPathsWithLengthUpto($Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AtomPathsWithLength$/i) { $PathsRef = $This->GetPathsWithLength($Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AtomPathsWithAllLengths$/i) { $PathsRef = $This->GetPaths($AllowCycles); last MODE; } + + if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetAllPathsWithLengthUpto($Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AllAtomPathsWithLength$/i) { $PathsRef = $This->GetAllPathsWithLength($Length, $AllowCycles); last MODE; } + if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { $PathsRef = $This->GetAllPaths($AllowCycles); last MODE; } + + print "Warn: ${ClassName}->_GetAtomPaths: No atom paths retrieved: Mode, $Mode, is not supported..."; + return \@AtomPaths; + } + return $This->_GetAtomPathsFromPaths($PathsRef); +} + +# Get atom paths as an array reference containing references to arrays with path atoms... +# +sub _GetAtomPathsFromPaths { + my($This, $PathsRef) = @_; + my($Path, @AtomPaths); + + @AtomPaths = (); + if (!defined $PathsRef) { + return \@AtomPaths; + } + if (!@{$PathsRef}) { + # Return an empty atom paths list... + return \@AtomPaths; + } + for $Path (@{$PathsRef}) { + my(@PathAtoms); + @PathAtoms = (); + @PathAtoms = $This->_GetAtomPathFromPath($Path); + + push @AtomPaths, \@PathAtoms; + } + return \@AtomPaths; +} + +# Generate an array of bond objects for an array of path atoms and return an array +# of bond objects... +# +sub GetAtomPathBonds { + my($This, @PathAtoms) = @_; + my(@Bonds); + + if (!@PathAtoms) { + # Return an empty ring bonds list... + return @Bonds; + } + my(@PathAtomIDs); + + @PathAtomIDs = (); + @PathAtomIDs = $This->_GetAtomsIDsFromAtoms(@PathAtoms); + + return $This->_GetPathBonds(@PathAtomIDs); +} + +# Map atom IDs in path to atoms and return a reference to an array containing ring atoms... +# +sub _GetAtomPathFromPath { + my($This, $Path) = @_; + my(@PathAtoms); + + @PathAtoms = (); + if (!defined $Path) { + # Return an empty atoms list... + return @PathAtoms; + } + + return $This->_GetPathAtoms($Path); +} + +# Get atom paths between two specified atoms as a reference to an array containing references +# to arrays with path atoms. For molecules with rings, atom paths array contains may contain +# two paths. +# +sub GetAtomPathsBetween { + my($This, $StartAtom, $EndAtom) = @_; + my(@AtomPaths); + + @AtomPaths = (); + if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) { + carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: Start atom is not defined or it doesn't exist..."; + return @AtomPaths; + } + if (!(defined($EndAtom) && $This->HasAtom($EndAtom))) { + carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: End atom is not defined or it doesn't exist..."; + return @AtomPaths; + } + return $This->_GetAtomPathsBetween($StartAtom, $EndAtom); +} + +# Get atom paths between two specified atoms as a reference to an array containing references +# to arrays with path atoms. +# +sub _GetAtomPathsBetween { + my($This, $StartAtom, $EndAtom) = @_; + my($StartAtomID, $EndAtomID, @Paths); + + $StartAtomID = $StartAtom->GetID(); + $EndAtomID = $EndAtom->GetID(); + + @Paths = (); + @Paths = $This->GetPathsBetween($StartAtomID, $EndAtomID); + + return $This->_GetAtomPathsFromPaths(\@Paths); +} + +# Get atom neighborhoods around a specified atom as an array containing references +# to arrays with neighborhood atoms at different radii upto specified radius... +# +sub GetAtomNeighborhoodsWithRadiusUpto { + my($This, $StartAtom, $Radius) = @_; + + return $This->_GetAtomNeighborhoods('RadiusUpto', $StartAtom, $Radius); +} + +# Get atom neighborhoods around a specified atom as an array containing references +# to arrays with neighborhood atoms at possible radii... +# +sub GetAtomNeighborhoods { + my($This, $StartAtom) = @_; + + return $This->_GetAtomNeighborhoods('AllRadii', $StartAtom, undef); +} + +# Get atom neighborhood around a specified atom, along with their successor connected atoms, collected +# with in a specified radius as a list containing references to lists with first value corresponding to neighborhood +# atom at a specific radius and second value as reference to a list containing its successor connected atoms. +# +# For a neighborhood atom at each radius level, the successor connected atoms correspond to the +# neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last +# radius level don't contain any successor atoms which fall outside the range of specified radius. +# +sub GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto { + my($This, $StartAtom, $Radius) = @_; + + return $This->_GetAtomNeighborhoods('WithSuccessorsAndRadiusUpto', $StartAtom, $Radius); +} + +# Get atom neighborhood around a specified atom, along with their successor connected atoms, collected +# at all radii as a list containing references to lists with first value corresponding to neighborhood +# atom at a specific radius and second value as reference to a list containing its successor connected atoms. +# +# For a neighborhood atom at each radius level, the successor connected atoms correspond to the +# neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last +# radius level don't contain any successor atoms which fall outside the range of specified radius. +# +# +sub GetAtomNeighborhoodsWithSuccessorAtoms { + my($This, $StartAtom) = @_; + + return $This->_GetAtomNeighborhoods('WithSuccessorsAndAllRadii', $StartAtom, undef); +} + +# Get atom neighborhoods... +# +sub _GetAtomNeighborhoods { + my($This, $Mode, $StartAtom, $Radius) = @_; + my(@AtomNeighborhoods); + + @AtomNeighborhoods = (); + + if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) { + carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Start atom is not defined or it doesn't exist..."; + return @AtomNeighborhoods; + } + if ($Mode =~ /^(RadiusUpto|WithSuccessorsAndRadiusUpto)$/i) { + if (!(defined($Radius) && $Radius > 0)) { + carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Radius is not defined or it's <= 0 ..."; + return @AtomNeighborhoods; + } + } + + # Collect neighborhood atom IDs... + my($StartAtomID, @NeighborhoodAtomIDs, @NeighborhoodAtomIDsWithSuccessors); + + @NeighborhoodAtomIDs = (); @NeighborhoodAtomIDsWithSuccessors = (); + $StartAtomID = $StartAtom->GetID(); + + MODE: { + if ($Mode =~ /^RadiusUpto$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVerticesWithRadiusUpto($StartAtomID, $Radius); last MODE; } + if ($Mode =~ /^AllRadii$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVertices($StartAtomID); last MODE; } + + if ($Mode =~ /^WithSuccessorsAndRadiusUpto$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessorsAndRadiusUpto($StartAtomID, $Radius); last MODE; } + if ($Mode =~ /^WithSuccessorsAndAllRadii$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessors($StartAtomID); last MODE; } + + print "Warn: ${ClassName}->_GetAtomNeighborhood: No atom neighborhoods retrieved: Mode, $Mode, is not supported..."; + return @AtomNeighborhoods; + } + if ($Mode =~ /^(RadiusUpto|AllRadii)$/i) { + return $This->_GetNeighborhoodAtomsFromAtomIDs(\@NeighborhoodAtomIDs); + } + elsif ($Mode =~ /^(WithSuccessorsAndRadiusUpto|WithSuccessorsAndAllRadii)$/i) { + return $This->_GetNeighborhoodAtomsWithSuccessorsFromAtomIDs(\@NeighborhoodAtomIDsWithSuccessors); + } + + return @AtomNeighborhoods; +} + +# Map neighborhood atom IDs to atoms... +# +sub _GetNeighborhoodAtomsFromAtomIDs { + my($This, $NeighborhoodsAtomIDsRef) = @_; + my($NeighborhoodAtomIDsRef, @AtomNeighborhoods); + + @AtomNeighborhoods = (); + for $NeighborhoodAtomIDsRef (@{$NeighborhoodsAtomIDsRef}) { + my(@AtomNeighborhood); + + @AtomNeighborhood = (); + @AtomNeighborhood = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomIDsRef}); + push @AtomNeighborhoods, \@AtomNeighborhood; + } + return @AtomNeighborhoods; +} + +# Map neighborhood atom IDs with successors to atoms... +# +sub _GetNeighborhoodAtomsWithSuccessorsFromAtomIDs { + my($This, $NeighborhoodsAtomIDsWithSuccessorsRef) = @_; + my($Depth, $NeighborhoodAtomIDsWithSuccessorsRef, $NeighborhoodAtomIDWithSuccessorsRef, $NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef, @AtomNeighborhoods); + + $Depth = 0; + @AtomNeighborhoods = (); + + # Go over neighborhoods at each level... + for $NeighborhoodAtomIDsWithSuccessorsRef (@{$NeighborhoodsAtomIDsWithSuccessorsRef}) { + @{$AtomNeighborhoods[$Depth]} = (); + + # Go over the neighborhood atoms and their successors at a specific level.. + for $NeighborhoodAtomIDWithSuccessorsRef (@{$NeighborhoodAtomIDsWithSuccessorsRef}) { + my($NeighborhoodAtom, @NeighborhoodAtomWithSuccessors, @NeighborhoodAtomSuccessorAtoms); + + @NeighborhoodAtomWithSuccessors = (); @NeighborhoodAtomSuccessorAtoms = (); + ($NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef) = @{$NeighborhoodAtomIDWithSuccessorsRef}; + + # Map atom IDs to atoms... + $NeighborhoodAtom = $This->_GetAtomFromAtomID($NeighborhoodAtomID); + if (@{$NeighborhoodAtomSuccessorsIDsRef}) { + @NeighborhoodAtomSuccessorAtoms = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomSuccessorsIDsRef}); + } + + # Store an atom and its successors at each level in an array... + push @NeighborhoodAtomWithSuccessors, ($NeighborhoodAtom, \@NeighborhoodAtomSuccessorAtoms); + + push @{$AtomNeighborhoods[$Depth]} , \@NeighborhoodAtomWithSuccessors; + } + $Depth++; + } + return @AtomNeighborhoods; +} + +# Get next object ID... +sub _GetNewObjectID { + $ObjectID++; + return $ObjectID; +} + +# Is aromatic property set for the molecule? +sub IsAromatic { + my($This) = @_; + my($Aromatic); + + $Aromatic = $This->GetAromatic(); + + return (defined($Aromatic) && $Aromatic) ? 1 : 0; +} + +# Does molecule contains any atoms with non-zero Z coordiantes? +sub IsThreeDimensional { + my($This) = @_; + my($Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + ATOM: for $Atom (@Atoms) { + if ($Atom->GetZ() != 0) { + return 1; + } + } + return 0; +} + +# Does molecule contains any atoms with non-zero X or Y coordinates +# and only zero Z-coordinates? +sub IsTwoDimensional { + my($This) = @_; + my($Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + ATOM: for $Atom (@Atoms) { + if ($Atom->GetZ() != 0) { + return 0; + } + if ($Atom->GetX() != 0 || $Atom->GetY() != 0) { + return 1; + } + } + return 0; +} + +# Get dimensionality of the molecule using one of the following two methods: +# . Using explicitly set Dimensionality +# . Going over atomic coordinates +# +# The valid dimensionality values are: +# . 3D - Three dimensional: One of X, Y or Z coordinate is non-zero +# . 2D - Two dimensional: One of X or Y coordinate is non-zero; All Z coordinates are zero +# . 0D - Zero dimensional: All atomic coordinates are zero +# +sub GetDimensionality { + my($This) = @_; + + # Is Dimensionality property explicitly set? + if ($This->HasProperty('Dimensionality')) { + return $This->GetProperty('Dimensionality'); + } + my($Atom, @Atoms); + + @Atoms = $This->GetAtoms(); + ATOM: for $Atom (@Atoms) { + if ($Atom->GetZ() != 0) { + return '3D'; + } + if ($Atom->GetX() != 0 || $Atom->GetY() != 0) { + return '2D'; + } + } + return '0D'; +} + +# Is it a molecule object? +sub IsMolecule ($) { + my($Object) = @_; + + return _IsMolecule($Object); +} + +# Return a string containing vertices, edges and other properties... +sub StringifyMolecule { + my($This) = @_; + my($MoleculeString, $ID, $Name, $NumOfAtoms, $NumOfBonds, $MolecularFormula, $NumOfRings, $MolecularWeight, $ExactMass, $FormalCharge, $SpinMultiplicity, $FreeRadicalElectrons, $Charge, $ElementsRef, $ElementsCompositionRef, $ElementalComposition); + + $ID = $This->GetID(); + $Name = $This->GetName(); + $NumOfAtoms = $This->GetNumOfAtoms(); + $NumOfBonds = $This->GetNumOfBonds(); + + $NumOfRings = $This->GetNumOfRings(); + if (!defined $NumOfRings) { + $NumOfRings = 'undefined'; + } + + $MolecularFormula = $This->GetMolecularFormula(); + + $MolecularWeight = $This->GetMolecularWeight(); + $MolecularWeight = round($MolecularWeight, 4) + 0; + + $ExactMass = $This->GetExactMass(); + $ExactMass = round($ExactMass, 4) + 0; + + $FormalCharge = $This->GetFormalCharge(); + $Charge = $This->GetCharge(); + + $SpinMultiplicity = $This->GetSpinMultiplicity(); + $FreeRadicalElectrons = $This->GetFreeRadicalElectrons(); + + ($ElementsRef, $ElementsCompositionRef) = $This->GetElementalComposition(); + $ElementalComposition = 'None'; + if (defined($ElementsRef) && @{$ElementsRef}) { + $ElementalComposition = "[ " . FormatElementalCompositionInformation($ElementsRef, $ElementsCompositionRef) . " ]"; + } + + $MoleculeString = "Molecule: ID: $ID; Name: \"$Name\"; NumOfAtoms: $NumOfAtoms; NumOfBonds: $NumOfBonds; NumOfRings: $NumOfRings; MolecularFormula: $MolecularFormula; MolecularWeight: $MolecularWeight; ExactMass: $ExactMass; FormalCharge: $FormalCharge; Charge: $Charge; SpinMultiplicity: $SpinMultiplicity; FreeRadicalElectrons: $FreeRadicalElectrons; ElementalComposition: $ElementalComposition"; + + return $MoleculeString; +} + +# Load appropriate atom data files from <MayaChemTools>/lib directory used by various +# object methods in the current class... +# +sub _LoadMoleculeClassData { + my($MayaChemToolsLibDir); + + $MayaChemToolsLibDir = GetMayaChemToolsLibDirName(); + + # Load and process data for aromaticity models... + _LoadAromaticityModelsData($MayaChemToolsLibDir); + _ProcessAromaticityModelsData(); +} + +# +# Load data for supported aromaticity models... +# +sub _LoadAromaticityModelsData { + my($MayaChemToolsLibDir) = @_; + my($DataFile, $Index, $InDelim, $Line, $NumOfCols, $ParameterName, $ParameterValue, $ModelName, @ColLabels, @LineWords, %ParameterNames, %ColIndexToModelName, %SupportedParameterNames); + + %AromaticityModelsDataMap = (); + %CanonicalAromaticityModelNamesMap = (); + + # File format: + # + # "ParameterName","MDLAromaticityModel","TriposAromaticityModel","MMFFAromaticityModel","ChemAxonBasicAromaticityModel","ChemAxonGeneralAromaticityModel","DaylightAromaticityModel","MayaChemToolsAromaticityModel" + # "AllowHeteroRingAtoms","No","No","Yes","Yes","Yes","Yes","Yes" + # + $DataFile = $MayaChemToolsLibDir . "/data/AromaticityModelsData.csv"; + if (! -e "$DataFile") { + croak "Error: ${ClassName}::_LoadAromaticityModelsData: MayaChemTools package file, $DataFile, is missing: Possible installation problems..."; + } + + # Setup a list of currently supported aromaticity parameters... + # + my(@KnownNames); + @KnownNames = qw(AllowHeteroRingAtoms HeteroRingAtomsList AllowExocyclicDoubleBonds AllowHomoNuclearExocyclicDoubleBonds AllowElectronegativeRingAtomExocyclicDoubleBonds AllowRingAtomFormalCharge AllowHeteroRingAtomFormalCharge MinimumRingSize); + + %SupportedParameterNames = (); + for $ParameterName (@KnownNames) { + $SupportedParameterNames{$ParameterName} = $ParameterName; + } + + $InDelim = "\,"; + open DATAFILE, "$DataFile" or croak "Couldn't open $DataFile: $! ..."; + + # Skip lines up to column labels... + LINE: while ($Line = GetTextLine(\*DATAFILE)) { + if ($Line !~ /^#/) { + last LINE; + } + } + @ColLabels= quotewords($InDelim, 0, $Line); + $NumOfCols = @ColLabels; + + %ColIndexToModelName = (); + + # Process names of aromaticity models... + for $Index (1 .. $#ColLabels) { + $ModelName = $ColLabels[$Index]; + $ModelName =~ s/ //g; + + if (exists $AromaticityModelsDataMap{$ModelName}) { + croak "Error: ${ClassName}::_LoadAromaticityModelsData: The aromaticity model name, $ModelName, in $DataFile has already exists.\nLine: $Line..."; + } + %{$AromaticityModelsDataMap{$ModelName}} = (); + + # Cannonicalize aromatic model name by converting into all lowercase... + $CanonicalAromaticityModelNamesMap{lc($ModelName)} = $ModelName; + + $ColIndexToModelName{$Index} = $ModelName; + } + + # Process paramater name and their values for specified aromaticity models... + # + %ParameterNames = (); + LINE: while ($Line = GetTextLine(\*DATAFILE)) { + if ($Line =~ /^#/) { + next LINE; + } + @LineWords = (); + @LineWords = quotewords($InDelim, 0, $Line); + if (@LineWords != $NumOfCols) { + croak "Error: ${ClassName}::_LoadAromaticityModelsData: The number of data fields, @LineWords, in $DataFile must be $NumOfCols.\nLine: $Line..."; + } + + # Process parameter name and values for aromaticity models... + # + $ParameterName = $LineWords[0]; + + if (!exists $SupportedParameterNames{$ParameterName}) { + carp "Warning: ${ClassName}::_LoadAromaticityModelsData: The current release of MayaChemTools doesn't support aromaticity model parameter name, $ParameterName, specified in $DataFile. It would be ignore during aromaticity detection.\nLine: $Line..."; + } + + if (exists $ParameterNames{$ParameterName}) { + carp "Warning: ${ClassName}::_LoadAromaticityModelsData: Ignoring aromaticity model data for parameter name, $ParameterName, in $DataFile. It has already been loaded.\nLine: $Line..."; + next LINE; + } + $ParameterNames{$ParameterName} = $ParameterName; + + for $Index (1 .. $#LineWords) { + $ModelName = $ColIndexToModelName{$Index}; + $ParameterValue = $LineWords[$Index]; + $AromaticityModelsDataMap{$ModelName}{$ParameterName} = $ParameterValue; + } + } + close DATAFILE; +} + +# Process already loaded aromaticity model data... +# +sub _ProcessAromaticityModelsData { + my($ParameterName, $ParameterValue, $ModelName, $NewParameterValue); + + for $ModelName (keys %AromaticityModelsDataMap) { + for $ParameterName (keys %{$AromaticityModelsDataMap{$ModelName}}) { + $ParameterValue = $AromaticityModelsDataMap{$ModelName}{$ParameterName}; + $ParameterValue =~ s/ //g; + + VALUE: { + if ($ParameterValue =~ /^Yes$/i) { + $NewParameterValue = 1; + last VALUE; + } + if ($ParameterValue =~ /^(NA|No)$/i) { + $NewParameterValue = 0; + last VALUE; + } + if ($ParameterValue =~ /^None$/i) { + $NewParameterValue = ''; + last VALUE; + } + $NewParameterValue = $ParameterValue; + } + $AromaticityModelsDataMap{$ModelName}{$ParameterName} = $NewParameterValue; + + if ($ParameterName =~ /List/i) { + # Setup a new parameter conatining a reference to a hash for the specified values... + my($DataMapRefName, $DataValue, %DataMap); + + $DataMapRefName = "${ParameterName}MapRef"; + + %DataMap = (); + for $DataValue (split /\,/, $NewParameterValue) { + $DataMap{$DataValue} = $DataValue; + } + $AromaticityModelsDataMap{$ModelName}{$DataMapRefName} = \%DataMap; + } + } + } +} + +# Is it a molecule object? +sub _IsMolecule { + my($Object) = @_; + + return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; +} + +1; + +__END__ + +=head1 NAME + +Molecule - Molecule class + +=head1 SYNOPSIS + +use Molecule; + +use Molecule qw(:all); + +=head1 DESCRIPTION + +B<Molecule> class provides the following methods: + +new, AddAtom, AddAtoms, AddBond, AddBonds, AddHydrogens, AddPolarHydrogens, +ClearRings, Copy, DeleteAromaticity, DeleteAtom, DeleteAtoms, DeleteBond, +DeleteBonds, DeleteHydrogens, DeletePolarHydrogens, DetectAromaticity, +DetectRings, FormatElementalCompositionInformation, GetAllAtomPaths, +GetAllAtomPathsStartingAt, GetAllAtomPathsStartingAtWithLength, +GetAllAtomPathsStartingAtWithLengthUpto, GetAllAtomPathsWithLength, +GetAllAtomPathsWithLengthUpto, GetAromaticRings, GetAromaticityModel, +GetAtomNeighborhoods, GetAtomNeighborhoodsWithRadiusUpto, +GetAtomNeighborhoodsWithSuccessorAtoms, +GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto, GetAtomPathBonds, +GetAtomPaths, GetAtomPathsBetween, GetAtomPathsStartingAt, +GetAtomPathsStartingAtWithLength, GetAtomPathsStartingAtWithLengthUpto, +GetAtomPathsWithLength, GetAtomPathsWithLengthUpto, GetAtoms, GetBonds, GetCharge, +GetConnectedComponents, GetConnectedComponentsAtoms, GetDimensionality, +GetElementalComposition, GetElementsAndNonElements, GetExactMass, GetFormalCharge, +GetFreeRadicalElectrons, GetFusedAndNonFusedRings, GetLargestConnectedComponent, +GetLargestConnectedComponentAtoms, GetLargestRing, GetMolecularFormula, +GetMolecularWeight, GetNumOfAromaticRings, GetNumOfAtoms, GetNumOfBonds, +GetNumOfConnectedComponents, GetNumOfElementsAndNonElements, GetNumOfHeavyAtoms, +GetNumOfHydrogenAtoms, GetNumOfMissingHydrogenAtoms, GetNumOfNonHydrogenAtoms, +GetNumOfRings, GetNumOfRingsWithEvenSize, GetNumOfRingsWithOddSize, +GetNumOfRingsWithSize, GetNumOfRingsWithSizeGreaterThan, +GetNumOfRingsWithSizeLessThan, GetRingBonds, GetRingBondsFromRings, GetRings, +GetRingsWithEvenSize, GetRingsWithOddSize, GetRingsWithSize, +GetRingsWithSizeGreaterThan, GetRingsWithSizeLessThan, GetSizeOfLargestRing, +GetSizeOfSmallestRing, GetSmallestRing, GetSpinMultiplicity, +GetSupportedAromaticityModels, GetTopologicallySortedAtoms, GetValenceModel, +HasAromaticAtomsInRings, HasAromaticAtomsNotInRings, HasAromaticRings, HasAtom, +HasBond, HasFusedRings, HasNoRings, HasOnlyOneRing, HasRings, IsAromatic, +IsMolecule, IsRingAromatic, IsSupportedAromaticityModel, IsThreeDimensional, +IsTwoDimensional, KeepLargestComponent, KekulizeAromaticAtoms, NewAtom, NewBond, +SetActiveRings, SetAromaticityModel, SetID, SetValenceModel, StringifyMolecule + +The following methods can also be used as functions: + +FormatElementalCompositionInformation, IsMolecule + +B<Molecule> class is derived from B<ObjectProperty> base class which provides methods not explicitly +defined in B<Molecule> or B<ObjectProperty> class using Perl's AUTOLOAD functionality. These methods +are generated on-the-fly for a specified object property: + + Set<PropertyName>(<PropertyValue>); + $PropertyValue = Get<PropertyName>(); + Delete<PropertyName>(); + +=head2 METHODS + +=over 4 + +=item B<new> + + $NewMolecule = new Molecule([%PropertyNameAndValues]); + +Using specified I<Atom> property names and values hash, B<new> method creates a new object +and returns a reference to newly created B<Atom> object. By default, the following properties are +initialized: + + ID = SequentialObjectID + Name = "Molecule <SequentialObjectID>" + +Examples: + + $Molecule = new Molecule(); + + $WaterMolecule = new Molecule('Name' => 'Water'); + + $Oxygen = new Atom('AtomSymbol' => 'O', 'XYZ' => [0, 0, 0]); + $Hydrogen1 = new Atom('AtomSymbol' => 'H', + 'XYZ' => [0.7144, 0.4125, 0]); + $Hydrogen2 = new Atom('AtomSymbol' => 'H', + 'XYZ' => [1.1208, -0.2959, 0]); + $WaterMolecule->AddAtoms($Oxygen, $Hydrogen1, $Hydrogen2); + + $Bond1 = new Bond('Atoms' => [$Oxygen, $Hydrogen1], + 'BondOrder' => 1); + $Bond2 = new Bond('Atoms' => [$Oxygen, $Hydrogen2], + 'BondOrder' => 1); + $WaterMolecule->AddBonds($Bond1, $Bond2); + +=item B<AddAtom> + + $Molecule->AddAtom($Atom); + +Adds an I<Atom> to a I<Molecule> and returns I<Molecule>. + +=item B<AddAtoms> + + $Molecule->AddAtoms(@Atoms); + +Adds I<Atoms> to a I<Molecule> and returns I<Molecule>. + +=item B<AddBond> + + $Molecule->AddBond($Bond); + +Adds a I<Bond> to a I<Molecule> and returns I<Molecule>. + +=item B<AddBonds> + + $Molecule->AddBonds(@Bonds); + +Adds I<Bonds> to a I<Molecule> and returns I<Molecule>. + +=item B<AddHydrogens> + + $NumOfHydrogensAdded = $Molecule->AddHydrogens(); + +Adds hydrogens to each atom in a I<Molecule> and returns total number of hydrogens +added. The current release of MayaChemTools doesn't assign hydrogen positions. + +=item B<AddPolarHydrogens> + + $NumOfHydrogensAdded = $Molecule->AddPolarHydrogens(); + +Adds hydrogens to each polar atom - N, O, P or S - in a I<Molecule> and returns total +number of polar hydrogens added. The current release of MayaChemTools doesn't +assign hydrogen positions. + +=item B<ClearRings> + + $Molecule->ClearRings(); + +Deletes all rings associated with I<Molecule> and returns I<Molecule>. + +=item B<Copy> + + $MoleculeCopy = $Molecule->Copy(); + +Copies I<Molecule> and its associated data using B<Storable::dclone> and returns a new +B<Molecule> object. + +=item B<DeleteAromaticity> + + $Molecule->DeleteAromaticity(); + +Deletes aromatic property associated with all atoms and bonds in a I<Molecule> and returns +I<Molecule>. + +=item B<DeleteAtom> + + $Molecule->DeleteAtom($Atom); + +Deletes I<Atom> from a I<Molecule> and returns I<Molecule>. + +=item B<DeleteAtoms> + + $Molecule->DeleteAtoms(@Atoms); + +Deletes I<Atoms> from a I<Molecule> and returns I<Molecule>. + +=item B<DeleteBond> + + $Molecule->DeleteBond($Bond); + +Deletes I<Bond> from a I<Molecule> and returns I<Molecule>. + +=item B<DeleteBonds> + + $Molecule->DeleteBonds(@Bonds); + +Deletes I<Bonds> from a I<Molecule> and returns I<Molecule>. + +=item B<DeleteHydrogens> + + $NumOfHydrogensDeleted = $Molecule->DeleteHydrogens(); + +Removes hydrogens from each atom in a I<Molecule> and returns total number of hydrogens +deleted. + +=item B<DeletePolarHydrogens> + + $NumOfHydrogensDeleted = $Molecule->DeletePolarHydrogens(); + +Removes hydrogens to each polar atom - N, O, P or S - in a I<Molecule> and returns total +number of polar hydrogens deleted. + +=item B<DetectAromaticity> + + $Molecule->DetectAromaticity(); + +Associates I<Aromatic> property to atoms and bonds involved in aromatic rings or ring +systems in a I<Molecule> and returns I<Molecule>. + +This method assumes the ring detection has already been perfomed using B<DetectRings>. +And any existing I<Aromatic> property associated with atoms and bonds is deleted before +performing aromaticity detection. + +What is aromaticity? [ Ref 124 ] It's in the code of the implementer, did you +say? Agree. The implementation of aromaticity varies widely across different +packages [ Ref 125 ]; additionally, the implementation details are not always +completely available, and it's not possible to figure out the exact implementation +of aromaticity across various packages. Using the publicly available information, +however, one can try to reproduce the available results to the extent possible, +along with parameterizing all the control parameters used to implement different +aromaticity models, and that's exactly what the current release of MayaChemTools +does. + +The implementation of aromaticity corresponding to various aromaticity models in +MayaChemTools package is driven by an external CSV file AromaticityModelsData.csv, +which is distributed with the package and is available in lib/data directory. The CSV +files contains names of supported aromaticity models, along with various control +parameters and their values. This file is loaded and processed during instantiation +of Molecule class and data corresponding to specific aromaticity model are used +to detect aromaticity for that model. Any new aromaticity model added to the +aromaticity data file, using different combinations of values for existing control +parameters, would work without any changes to the code; the addition of any new +control parameters, however, requires its implementation in the code used to +calculate number of pi electrons available towards delocalization in a ring or ring +systems. + +The current release of MayaChemTools package supports these aromaticity +models: MDLAromaticityModel, TriposAromaticityModel, MMFFAromaticityModel, +ChemAxonBasicAromaticityModel, ChemAxonGeneralAromaticityModel, +DaylightAromaticityModel, MayaChemToolsAromaticityModel. + +The current list of control parameters available to detect aromaticity corresponding +to different aromaticity models are: AllowHeteroRingAtoms, HeteroRingAtomsList, +AllowExocyclicDoubleBonds, AllowHomoNuclearExocyclicDoubleBonds, +AllowElectronegativeRingAtomExocyclicDoubleBonds, AllowRingAtomFormalCharge, +AllowHeteroRingAtomFormalCharge, MinimumRingSize. The values for these control +parameters are specified in AromaticityModelsData.csv file. + +Although definition of aromaticity differs across various aromaticity models, a ring +or a ring system containing 4n + 2 pi electrons (Huckel's rule) corresponding to +alternate single and double bonds, in general, is considered aromatic. + +The available valence free electrons on heterocyclic ring atoms, involved in two single +ring bonds, are also allowed to participate in pi electron delocalizaiton for most of +the supported aromaticity models. + +The presence of exocyclic terminal double bond on ring atoms involved in pi electron +delocalization is only allowed for some of the aromaticity models. Additionally, the type +atoms involved in exocyclic terminal double bonds may result in making a ring or ring +system non-aromatic. + +For molecules containing fused rings, each fused ring set is considered as one aromatic +system for counting pi electrons to satisfy Huckel's rule; In case of a failure, rings in +fused set are treated individually for aromaticity detection. Additionally, non-fused +rings are handled on their own during aromaticity detection. + +=item B<DetectRings> + + $Molecule->DetectRings(); + +Detects rings in a I<Molecule> and returns I<Molecule>. Ring detection is performed using +B<DetectCycles> method avaible in B<Graph> class which in turn uses methods available +B<Graph::CyclesDetection> class. B<Graph::CyclesDetection> class implements collapsing path graph +[Ref 31] methodology to detect all cycles in a graph. + +=item B<FormatElementalCompositionInformation> + + $FormattedInfo = $Molecule->FormatElementalCompositionInformation( + $ElementsRef, $ElementCompositionRef, + [$Precision]); + $FormattedInfo = Molecule::FormatElementalCompositionInformation( + $ElementsRef, $ElementCompositionRef, + [$Precision]); + +Using I<ElementsRef> and I<ElementCompositionRef> arrays referneces containg informatio +about elements and their composition, formats elemental composition information and returns +a I<FormattedInfo> string. Defaule I<Precision> value: I<2>. + +=item B<GetAromaticityModel> + + $AromaticityModel = $Molecule->GetAromaticityModel(); + +Returns name of B<AromaticityModel> set for I<Molecule> corresponding to B<AromaticityModel> +property or default model name of B<MayaChemToolsAromaticityModel>. + +=item B<GetAllAtomPaths> + + $AtomPathsRef = $Molecule->GetAllAtomPaths([$AllowCycles]); + +Returns all paths as a reference to an array containing reference to arrays with path +B<Atom> objects. + +Path atoms correspond to to all possible paths for each atom in molecule with all +possible lengths and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +B<GetAtomPaths> method. + +=item B<GetAllAtomPathsStartingAt> + + $AtomPathsRef = $Molecule->GetAllAtomPathsStartingAt($StartAtom, + [$AllowCycles]); + +Returns all atom paths starting from I<StartAtom> as a reference to an array containing +reference to arrays with path B<Atom> objects. + +Path atoms atoms correspond to to all possible paths for specified atom in molecule with all +possible lengths and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +B<GetAtomPathsStartingAt> method. + +=item B<GetAllAtomPathsStartingAtWithLength> + + $AtomPathsRef = $Molecule->GetAllAtomPathsStartingAtWithLength( + $StartAtom, $Length, [$AllowCycles]); + +Returns all atom paths starting from I<StartAtom> with specified I<Length>as a reference +to an array containing reference to arrays with path B<Atom> objects. + +Path atoms atoms correspond to to all possible paths for specified atom in molecule with all +possible lengths and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +B<GetAtomPathsStartingAtWithLength> method. + +=item B<GetAllAtomPathsStartingAtWithLengthUpto> + + $AtomPathsRef = $Molecule->GetAllAtomPathsStartingAtWithLengthUpto( + $StartAtom, $Length, [$AllowCycles]); + +Returns atom paths starting from I<StartAtom> with length up to I<Length> as a reference +to an array containing reference to arrays with path B<Atom> objects. + +Path atoms atoms correspond to all possible paths for specified atom in molecule with length +up to a specified length and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +I<GetAtomPathsStartingAtWithLengthUpto> method. + +=item B<GetAllAtomPathsWithLength> + + $AtomPathsRef = $Molecule->GetAllAtomPathsWithLength($Length, + [$AllowCycles]); + +Returns all atom paths with specified I<Length> as a reference to an array containing +reference to arrays with path B<Atom> objects. + +Path atoms correspond to to all possible paths for each atom in molecule with length +up to a specified length and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +as I<GetAtomPathsWithLength> method. + +=item B<GetAllAtomPathsWithLengthUpto> + + $AtomPathsRef = $Molecule->GetAllAtomPathsWithLengthUpto($Length, + [$AllowCycles]); + +Returns all atom paths with length up to I<Length> as a reference to an array containing +reference to arrays with path B<Atom> objects. + +Path atoms correspond to to all possible paths for each atom in molecule with length +up to a specified length and sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +For molecule without any rings, this method returns the same set of atom paths as +as I<GetAtomPathsWithLengthUpto> method. + +=item B<GetAromaticRings> + + @AtomaticRings = $Molecule->GetAromaticRings(); + +Returns aromatic rings as an array containing references to arrays of ring I<Atom> objects +in a I<Molecule>. + +=item B<GetAtomNeighborhoods> + + @Neighborhoods = $Molecule->GetAtomNeighborhoods($StartAtom); + +Returns atom neighborhoods around a I<StartAtom> as an array containing references +to arrays with neighborhood I<Atom> objects at possible radii. + +=item B<GetAtomNeighborhoodsWithRadiusUpto> + + @Neighborhoods = $Molecule->GetAtomNeighborhoodsWithRadiusUpto($StartAtom, + $Radius); + +Returns atom neighborhoods around a I<StartAtom> as an array containing references +to arrays with neighborhood I<Atom> objects up to I<Radius>. + +=item B<GetAtomNeighborhoodsWithSuccessorAtoms> + + @Neighborhoods = $Molecule->GetAtomNeighborhoodsWithSuccessorAtoms( + $StartAtom); + +Returns atom neighborhood around a specified I<StartAtom>, along with their successor +connected atoms, collected at all radii as an array containing references to arrays with first +value corresponding to neighborhood atom at a specific radius and second value as reference +to an array containing its successor connected atoms. + +For a neighborhood atom at each radius level, the successor connected atoms correspond to the +neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last +radius level don't contain any successor atoms which fall outside the range of specified radius. + +=item B<GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto> + + @Neighborhoods = $Molecule->GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto( + $StartAtom, $Radius); + +Returns atom neighborhood around a specified I<StartAtom>, along with their successor +connected atoms, collected upto specified I<Radiud> as an array containing references to arrays +with first value corresponding to neighborhood atom at a specific radius and second value as +reference to an array containing its successor connected atoms. + +For a neighborhood atom at each radius level, the successor connected atoms correspond to the +neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last +radius level don't contain any successor atoms which fall outside the range of specified radius. + +=item B<GetAtomPathBonds> + + $Return = $Molecule->GetAtomPathBonds(@PathAtoms); + +Returns an array containing B<Bond> objects corresponding to successive pair of +atoms in I<PathAtoms> + +=item B<GetAtomPaths> + + $AtomPathsRef = $Molecule->GetAtomPaths([$AllowCycles]); + +Returns all paths as a reference to an array containing reference to arrays with path +B<Atom> objects. + +Path atoms correspond to to all possible paths for each atom in molecule with all +possible lengths and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtomPathsBetween> + + $AtomPathsRef = $Molecule->GetAtomPathsBetween($StartAtom, $EndAtom); + +Returns all paths as between I<StartAtom> and I<EndAtom> as a reference to an array +containing reference to arrays with path B<Atom> objects. + +For molecules with rings, atom paths array contains may contain two paths. + +=item B<GetAtomPathsStartingAt> + + $AtomPathsRef = $Molecule->GetAtomPathsStartingAt($StartAtom, [$AllowCycles]); + +Returns paths starting at I<StartAtom> as a reference to an array containing reference to +arrays with path B<Atom> objects. + +Path atoms correspond to all possible paths for specified atom in molecule with all +possible lengths and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtomPathsStartingAtWithLength> + + $AtomPathsRef = $Molecule->GetAtomPathsStartingAtWithLength($StartAtom, + $Length, [$AllowCycles]); + +Returns paths starting at I<StartAtom> with length I<Length> as a reference to an array +containing reference to arrays with path B<Atom> objects. + +Path atoms correspond to all possible paths for specified atom in molecule with length +upto a specified length and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtomPathsStartingAtWithLengthUpto> + + $AtomPathsRef = $Molecule->GetAtomPathsStartingAtWithLengthUpto($StartAtom, + $Length, [$AllowCycles]); + +Returns paths starting at I<StartAtom> with length up to I<Length> as a reference to an array +containing reference to arrays with path B<Atom> objects. + +Path atoms correspond to all possible paths for specified atom in molecule with length +upto a specified length and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtomPathsWithLength> + + $AtomPathsRef = $Molecule->GetAtomPathsWithLength($Length, [$AllowCycles]); + +Returns all paths with specified I<Length> as a reference to an array containing reference +to arrays with path B<Atom> objects. + +Path atoms correspond to all possible paths for each atom in molecule with length +upto a specified length and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtomPathsWithLengthUpto> + + $AtomPathsRef = $Molecule->GetAtomPathsWithLengthUpto($Length, [$AllowCycles]); + +Returns all paths with length up to I<Length> as a reference to an array containing reference +to arrays with path B<Atom> objects. + +Path atoms correspond to all possible paths for each atom in molecule with length +upto a specified length and no sharing of bonds in paths traversed. By default, rings are +included in paths. A path containing a ring is terminated at an atom completing the ring. + +=item B<GetAtoms> + + @AllAtoms = $Molecule->GetAtoms(); + @PolarAtoms = $Molecule->GetAtoms('IsPolarAtom'); + + $NegateMethodResult = 1; + @NonHydrogenAtoms = $Molecule->GetAtoms('IsHydrogenAtom', + $NegateMethodResult); + + $AtomsCount = $Molecule->GetAtoms(); + +Returns an array of I<Atoms> in a I<Molecule>. In scalar context, it returns number of atoms. +Additionally, B<Atoms> array can be filtered by any user specifiable valid B<Atom> class method +and the result of the B<Atom> class method used to filter the atoms can also be negated by +an optional negate results flag as third parameter. + +=item B<GetBonds> + + @Bonds = $Molecule->GetBonds(); + $BondsCount = $Molecule->GetBonds(); + +Returns an array of I<Bonds> in a I<Molecule>. In scalar context, it returns number of bonds. + +=item B<GetCharge> + + $Charge = $Molecule->GetCharge(); + +Returns net charge on a I<Molecule> using one of the following two methods: explicitly +set B<Charge> property or sum of partial atomic charges on each atom. + +=item B<GetConnectedComponents> + + @ConnectedComponents = $Molecule->GetConnectedComponents(); + +Returns a reference to an array containing I<Molecule> objects corresponding +to connected components sorted in decreasing order of component size in a I<Molecule>. + +=item B<GetConnectedComponentsAtoms> + + @ConnectedComponentsAtoms = + $Molecule->GetConnectedComponentsAtoms(); + +Returns an array containing references to arrays with I<Atom> objects corresponding to +atoms of connected components sorted in order of component decreasing size in a +I<Molecule>. + +=item B<GetDimensionality> + + $Dimensionality = $Molecule->GetDimensionality(); + +Returns I<Dimensionality> of a I<Molecule> corresponding to explicitly set +I<Dimensionality> property value or by processing atomic. + +The I<Dimensionality> value from atomic coordinates is calculated as follows: + + 3D - Three dimensional: One of X, Y or Z coordinate is non-zero + 2D - Two dimensional: One of X or Y coordinate is non-zero; All Z + coordinates are zero + 0D - Zero dimensional: All atomic coordinates are zero + +=item B<GetElementalComposition> + + ($ElementsRef, $CompositionRef) = + $Molecule->GetElementalComposition([$IncludeMissingHydrogens]); + +Calculates elemental composition and returns references to arrays containing elements +and their percent composition in a I<Molecule>. By default, missing hydrogens are included +during the calculation. + +=item B<GetElementsAndNonElements> + + ($ElementsRef, $NonElementsRef) = + $Molecule->GetElementsAndNonElements([$IncludeMissingHydrogens]); + +Counts elements and non-elements in a I<Molecule> and returns references to hashes +containing element and non-element as hash keys with values corresponding to their +count. By default, missing hydrogens are not added to the element hash. + +=item B<GetExactMass> + + $ExactMass = $Molecule->GetExactMass(); + +Returns exact mass of a I<Molecule> corresponding to sum of exact masses of all +the atoms. + +=item B<GetFormalCharge> + + $FormalCharge = $Molecule->GetFormalCharge(); + +Returns net formal charge on a I<Molecule> using one of the following two methods: explicitly +set B<FormalCharge> property or sum of formal charges on each atom. + +B<FormalCharge> is different from B<Charge> property of the molecule which corresponds to +sum of partial atomic charges explicitly set for each atom using a specific methodology. + +=item B<GetFreeRadicalElectrons> + + $FreeRadicalElectrons = $Molecule->GetFreeRadicalElectrons(); + +Returns total number of free radical electrons available in a I<Molecule> using one of the +following two methods: explicitly set B<FreeRadicalElectrons> property or sum of available +free radical electrons on each atom. + +=item B<GetFusedAndNonFusedRings> + + ($FusedRingSetRef, $NonFusedRingsRef) = + $Molecule->GetFusedAndNonFusedRings(); + +Returns references to array of fused ring sets and non-fused rings in a I<Molecule>. Fused ring sets +array reference contains refernces to arrays of rings corresponding to ring I<Atom> objects; +Non-fused rings array reference contains references to arrays of ring I<Atom> objects. + +=item B<GetLargestConnectedComponent> + + $ComponentMolecule = $Molecule->GetLargestConnectedComponent(); + +Returns a reference to B<Molecule> object corresponding to a largest connected component +in a I<Molecule>. + +=item B<GetLargestConnectedComponentAtoms> + + @ComponentAtoms = $Molecule->GetLargestConnectedComponentAtoms(); + +Returns a reference to an array of B<Atom> objects corresponding to a largest connected +component in a I<Molecule>. + +=item B<GetLargestRing> + + @RingAtoms = $Molecule->GetLargestRing(); + +Returns an array of I<Atoms> objects corresponding to a largest ring in a I<Molecule>. + +=item B<GetMolecularFormula> + + $FormulaString = $Molecule->GetMolecularFormula( + [$IncludeMissingHydrogens, + $IncludeNonElements]); + +Returns molecular formula of a I<Molecule> by collecting information about all atoms in +the molecule and composing the formula using Hills ordering system: + + o C shows up first and H follows assuming C is present. + o All other standard elements are sorted alphanumerically. + o All other non-stanard atom symbols are also sorted + alphanumerically and follow standard elements. + +Notes: + + o By default, missing hydrogens and nonelements are also included. + o Elements for disconnected fragments are combined into the same + formula. + o Formal charge is also used during compoisiton of molecular formula. + +=item B<GetMolecularWeight> + + $MolWeight = $Molecule->GetMolecularWeight(); + +Returns molecular weight of a I<Molecule> corresponding to sum of atomic weights of all +the atoms. + +=item B<GetNumOfAromaticRings> + + $NumOfAromaticRings = $Molecule->GetNumOfAromaticRings(); + +Returns number of aromatic rings in a I<Molecule>. + +=item B<GetNumOfAtoms> + + $NumOfAtoms = $Molecule->GetNumOfAtoms(); + +Returns number of atoms in a I<Molecule>. + +=item B<GetNumOfBonds> + + $NumOfBonds = $Molecule->GetNumOfBonds(); + +Returns number of bonds in a I<Molecule>. + +=item B<GetNumOfConnectedComponents> + + $NumOfComponents = $Molecule->GetNumOfConnectedComponents(); + +Returns number of connected components in a I<Molecule>. + +=item B<GetNumOfElementsAndNonElements> + + ($NumOfElements, $NumOfNonElements) = $Molecule-> + GetNumOfElementsAndNonElements(); + ($NumOfElements, $NumOfNonElements) = $Molecule-> + GetNumOfElementsAndNonElements($IncludeMissingHydrogens); + +Returns number of elements and non-elements in a I<Molecule>. By default, missing +hydrogens are not added to element count. + +=item B<GetNumOfHeavyAtoms> + + $NumOfHeavyAtoms = $Molecule->GetNumOfHeavyAtoms(); + +Returns number of heavy atoms, non-hydrogen atoms, in a I<Molecule>. + +=item B<GetNumOfHydrogenAtoms> + + $NumOfHydrogenAtoms = $Molecule->GetNumOfHydrogenAtoms(); + +Returns number of hydrogen atoms in a I<Molecule>. + +=item B<GetNumOfMissingHydrogenAtoms> + + $NumOfMissingHydrogenAtoms = $Molecule->GetNumOfMissingHydrogenAtoms(); + +Returns number of hydrogen atoms in a I<Molecule>. + +=item B<GetNumOfNonHydrogenAtoms> + + $NumOfNonHydrogenAtoms = $Molecule->GetNumOfNonHydrogenAtoms(); + +Returns number of non-hydrogen atoms in a I<Molecule>. + +=item B<GetNumOfRings> + + $RingCount = $Molecule->GetNumOfRings(); + +Returns number of rings in a I<Molecule>. + +=item B<GetNumOfRingsWithEvenSize> + + $RingCount = $Molecule->GetNumOfRingsWithEvenSize(); + +Returns number of rings with even size in a I<Molecule>. + +=item B<GetNumOfRingsWithOddSize> + + $RingCount = $Molecule->GetNumOfRingsWithOddSize(); + +Returns number of rings with odd size in a I<Molecule>. + +=item B<GetNumOfRingsWithSize> + + $RingCount = $Molecule->GetNumOfRingsWithSize($Size); + +Returns number of rings with I<Size> in a I<Molecule>. + +=item B<GetNumOfRingsWithSizeGreaterThan> + + $RingCount = $Molecule->GetNumOfRingsWithSizeGreaterThan($Size); + +Returns number of rings with size greater than I<Size> in a I<Molecule>. + +=item B<GetNumOfRingsWithSizeLessThan> + + $RingCount = $Molecule->GetNumOfRingsWithSizeLessThan($Size); + +Returns number of rings with size less than I<Size> in a I<Molecule>. + +=item B<GetRingBonds> + + @RingBonds = $Molecule->GetRingBonds(@RingAtoms); + +Returns an array of ring B<Bond> objects correponding to an array of ring I<Atoms> in a +I<Molecule>. + +=item B<GetRingBondsFromRings> + + @RingBondsSets = $Molecule->GetRingBondsFromRings(@RingAtomsSets); + +Returns an array containing references to arrays of ring B<Bond> objects for rings specified +in an array of references to ring I<Atom> objects. + +=item B<GetRings> + + @Rings = $Molecule->GetRings(); + +Returns rings as an array containing references to arrays of ring I<Atom> objects in a I<Molecule>. + +=item B<GetRingsWithEvenSize> + + @Rings = $Molecule->GetRingsWithEvenSize(); + +Returns even size rings as an array containing references to arrays of ring I<Atom> objects in +a I<Molecule>. + +=item B<GetRingsWithOddSize> + + @Rings = $Molecule->GetRingsWithOddSize(); + +Returns odd size rings as an array containing references to arrays of ring I<Atom> objects in +a I<Molecule>. + +=item B<GetRingsWithSize> + + @Rings = $Molecule->GetRingsWithSize($Size); + +Returns rings with I<Size> as an array containing references to arrays of ring I<Atom> objects in +a I<Molecule>. + +=item B<GetRingsWithSizeGreaterThan> + + @Rings = $Molecule->GetRingsWithSizeGreaterThan($Size); + +Returns rings with size greater than I<Size> as an array containing references to arrays of +ring I<Atom> objects in a I<Molecule>. + +=item B<GetRingsWithSizeLessThan> + + @Rings = $Molecule->GetRingsWithSizeLessThan($Size); + +Returns rings with size less than I<Size> as an array containing references to arrays of +ring I<Atom> objects in a I<Molecule>. + +=item B<GetSizeOfLargestRing> + + $Size = $Molecule->GetSizeOfLargestRing(); + +Returns size of the largest ring in a I<Molecule>. + +=item B<GetSizeOfSmallestRing> + + $Size = $Molecule->GetSizeOfSmallestRing(); + +Returns size of the smalles ring in a I<Molecule>. + +=item B<GetSmallestRing> + + @RingAtoms = $Molecule->GetSmallestRing(); + +Returns an array containing I<Atom> objects corresponding to the smallest ring in +a I<Molecule>. + +=item B<GetSpinMultiplicity> + + $SpinMultiplicity = $Molecule->GetSpinMultiplicity(); + +Returns net spin multiplicity of a I<Molecule> using one of the following two methods: explicitly +set B<SpinMultiplicity> property or sum of spin multiplicity on each atom. + +=item B<GetSupportedAromaticityModels> + + @SupportedModels = $Molecule->GetSupportedAromaticityModels(); + +Returns an array containing a list of supported aromaticity models. + +=item B<GetValenceModel> + + $ValenceModel = $Molecule->GetValenceModel(); + +Returns valence model for I<Molecule> using one of the following two methods: explicitly +set B<ValenceModel> property or defaul value of I<InternalValenceModel>. + +=item B<GetTopologicallySortedAtoms> + + @SortedAtoms = $Molecule->GetTopologicallySortedAtoms([$StartAtom]); + +Returns an array of topologically sorted I<Atom> objects starting from I<StartAtom> or +an arbitrary atom in a I<Molecule>. + +=item B<HasAromaticRings> + + $Status = $Molecule->HasAromaticRings(); + +Returns 1 or 0 based on whether any aromatic ring is present in a I<Molecule>. + +=item B<HasAromaticAtomsInRings> + + $Status = $Molecule->HasAromaticAtomsInRings(); + +Returns 1 or 0 based on whether any aromatic ring atom is present in a I<Molecule>. + +=item B<HasAromaticAtomsNotInRings> + + $Status = $Molecule->HasAromaticAtomsNotInRings(); + +Returns 1 or 0 based on whether any non-ring atom is marked aromatic in a I<Molecule>. + +=item B<HasAtom> + + $Status = $Molecule->HasAtom($Atom); + +Returns 1 or 0 based on whether I<Atom> is present in a I<Molecule>. + +=item B<HasBond> + + $Status = $Molecule->HasBond($Bond); + +Returns 1 or 0 based on whether I<Bond> is present in a I<Molecule>. + +=item B<HasFusedRings> + + $Status = $Molecule->HasFusedRings(); + +Returns 1 or 0 based on whether any fused rings set is present in a I<Molecule>. + +=item B<HasNoRings> + + $Status = $Molecule->HasNoRings(); + +Returns 0 or 1 based on whether any ring is present in a I<Molecule>. + +=item B<HasOnlyOneRing> + + $Status = $Molecule->HasOnlyOneRing(); + +Returns 1 or 0 based on whether only one ring is present in a I<Molecule>. + +=item B<HasRings> + + $Status = $Molecule->HasRings(); + +Returns 1 or 0 based on whether rings are present in a I<Molecule>. + +=item B<IsAromatic> + + $Status = $Molecule->IsAromatic(); + +Returns 1 or 0 based on whether I<Molecule> is aromatic. + +=item B<IsMolecule> + + $Status = Molecule::IsMolecule(); + +Returns 1 or 0 based on whether I<Object> is a B<Molecule> object. + +=item B<IsRingAromatic> + + $Status = $Molecule->IsRingAromatic(@RingAtoms); + +Returns 1 or 0 based on whether all I<RingAtoms> are aromatic. + +=item B<IsSupportedAromaticityModel> + + $Status = $Molecule->IsSupportedAromaticityModel($AromaticityModel); + $Status = Molecule::IsSupportedAromaticityModel($AromaticityModel); + +Returns 1 or 0 based on whether specified I<AromaticityModel> is supported. + +=item B<IsTwoDimensional> + + $Status = $Molecule->IsTwoDimensional(); + +Returns 1 or 0 based on whether any atom in I<Molecule> has a non-zero value +for X or Y coordinate and all atoms have zero value for Z coordinates. + +=item B<IsThreeDimensional> + + $Status = $Molecule->IsThreeDimensional(); + +Returns 1 or 0 based on whether any atom in I<Molecule> has a non-zero value +for Z coordinate. + +=item B<KeepLargestComponent> + + $Molecule->KeepLargestComponent(); + +Deletes atoms corresponding to all other connected components Except for the largest +connected component in a I<Molecule> and returns I<Molecule>. + +=item B<KekulizeAromaticAtoms> + + $Status = $Molecule->KekulizeAromaticAtoms(); + +Kekulize marked ring and non-ring aromatic atoms in a molecule and return 1 or 1 based +on whether the kekulization succeeded. + +=item B<NewAtom> + + $NewAtom = $Molecule->NewAtom(%AtomPropertyNamesAndValues); + +Creates a new atom using I<AtomPropertyNamesAndValues>, add its to I<Molecule>, and returns +new B<Atom> object. + +=item B<NewBond> + + $NewBond = $Molecule->NewBond(%BondPropertyNamesAndValues); + +Creates a new bond using I<AtomPropertyNamesAndValues>, add its to I<Molecule>, and returns +new B<Bond> object. + +=item B<SetActiveRings> + + $Molecule->SetActiveRings($RingsType); + +Sets up type of detected ring sets to use during all ring related methods and returns I<Molecule>. +Possible I<RingType> values: I<Independent or All>. By default, I<Independent> ring set is used +during all ring methods. + +=item B<SetAromaticityModel> + + $Molecule = $Molecule->SetAromaticityModel($AromaticityModel); + +Sets up I<AromaticityModel> property value for I<Molecule> and retrurns I<Molecule>. + +=item B<SetValenceModel> + + $Molecule = $Molecule->SetValenceModel(ValenceModel); + +Sets up I<ValenceModel> property value for I<Molecule> and retrurns I<Molecule>. + +=item B<StringifyMolecule> + + $MoleculeString = $Molecule->StringifyMolecule(); + +Returns a string containing information about I<Molecule> object + +=back + +=head1 AUTHOR + +Manish Sud <msud@san.rr.com> + +=head1 SEE ALSO + +Atom.pm, Bond.pm, MoleculeFileIO.pm, MolecularFormula.pm + +=head1 COPYRIGHT + +Copyright (C) 2015 Manish Sud. All rights reserved. + +This file is part of MayaChemTools. + +MayaChemTools is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) +any later version. + +=cut