MayaChemTools

   1 package Atom;
   2 #
   3 # File: Atom.pm
   4 # Author: Manish Sud <msud@san.rr.com>
   5 #
   6 # Copyright (C) 2024 Manish Sud. All rights reserved.
   7 #
   8 # This file is part of MayaChemTools.
   9 #
  10 # MayaChemTools is free software; you can redistribute it and/or modify it under
  11 # the terms of the GNU Lesser General Public License as published by the Free
  12 # Software Foundation; either version 3 of the License, or (at your option) any
  13 # later version.
  14 #
  15 # MayaChemTools is distributed in the hope that it will be useful, but without
  16 # any warranty; without even the implied warranty of merchantability of fitness
  17 # for a particular purpose.  See the GNU Lesser General Public License for more
  18 # details.
  19 #
  20 # You should have received a copy of the GNU Lesser General Public License
  21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
  22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
  23 # Boston, MA, 02111-1307, USA.
  24 #
  25 
  26 use strict;
  27 use Carp;
  28 use Exporter;
  29 use Storable ();
  30 use Scalar::Util ();
  31 use ObjectProperty;
  32 use PeriodicTable;
  33 use Vector;
  34 use MathUtil;
  35 use Text::ParseWords;
  36 use TextUtil;
  37 use FileUtil;
  38 
  39 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  40 
  41 @ISA = qw(ObjectProperty Exporter);
  42 @EXPORT = qw();
  43 @EXPORT_OK = qw();
  44 
  45 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  46 
  47 # Setup class variables...
  48 my($ClassName, $ObjectID, %MDLValenceModelDataMap, %DaylightValenceModelDataMap);
  49 _InitializeClass();
  50 
  51 # Overload Perl functions...
  52 use overload '""' => 'StringifyAtom';
  53 
  54 # Class constructor...
  55 sub new {
  56   my($Class, %NamesAndValues) = @_;
  57 
  58   # Initialize object...
  59   my $This = {};
  60   bless $This, ref($Class) || $Class;
  61   $This->_InitializeAtom();
  62 
  63   $This->_InitializeAtomProperties(%NamesAndValues);
  64 
  65   return $This;
  66 }
  67 
  68 # Initialize object data...
  69 sub _InitializeAtom {
  70   my($This) = @_;
  71   my($ObjectID) = _GetNewObjectID();
  72 
  73   # All other property names and values along with all Set/Get<PropertyName> methods
  74   # are implemented on-demand using ObjectProperty class.
  75   $This->{ID} = $ObjectID;
  76   $This->{Name} = "Atom ${ObjectID}";
  77   $This->{AtomSymbol} = '';
  78   $This->{AtomicNumber} = 0;
  79   $This->{XYZ} = Vector::ZeroVector;
  80 }
  81 
  82 # Initialize atom properties...
  83 sub _InitializeAtomProperties {
  84   my($This, %NamesAndValues) = @_;
  85 
  86   my($Name, $Value, $MethodName);
  87   while (($Name, $Value) = each  %NamesAndValues) {
  88     $MethodName = "Set${Name}";
  89     $This->$MethodName($Value);
  90   }
  91   if (!exists $NamesAndValues{'AtomSymbol'}) {
  92     carp "Warning: ${ClassName}->new: Atom object instantiated without setting atom symbol...";
  93   }
  94 
  95   return $This;
  96 }
  97 
  98 # Initialize class ...
  99 sub _InitializeClass {
 100   #Class name...
 101   $ClassName = __PACKAGE__;
 102 
 103   # ID to keep track of objects...
 104   $ObjectID = 0;
 105 
 106   # Load atom class data...
 107   _LoadAtomClassData();
 108 }
 109 
 110 # Setup an explicit SetID method to block setting of ID by AUTOLOAD function...
 111 sub SetID {
 112   my($This, $Value) = @_;
 113 
 114   carp "Warning: ${ClassName}->SetID: Object ID can't be changed: it's used for internal tracking...";
 115 
 116   return $This;
 117 }
 118 
 119 # Setup an explicit SetMolecule method to block setting of ID by AUTOLOAD function...
 120 sub SetMolecule {
 121   my($This, $Value) = @_;
 122 
 123   carp "Warning: ${ClassName}->SetMolecule: Molecule property can't be changed: it's used for internal tracking...";
 124 
 125   return $This;
 126 }
 127 
 128 # Assign atom to  molecule...
 129 sub _SetMolecule {
 130   my($This, $Molecule) = @_;
 131 
 132   $This->{Molecule} = $Molecule;
 133 
 134   # Weaken the reference to disable increment of reference count; otherwise,
 135   # it it becomes a circular reference and destruction of Molecule object doesn't
 136   # get initiated which in turn disables destruction of atom object.
 137   #
 138   Scalar::Util::weaken($This->{Molecule});
 139 
 140   return $This;
 141 }
 142 
 143 # Setup atom symbol and atomic number for the element...
 144 #
 145 # Possible atom symbol values:
 146 #    . An element symbol or some other type of atom: L - Atom list; LP - Lone pair; R# - R group;
 147 #       A, Q, * - unknown atom; or something else?
 148 #
 149 # Default mass number corresponds to the most abundant natural isotope unless it's explicity
 150 # set using "MassNumber" property.
 151 #
 152 sub SetAtomSymbol {
 153   my($This, $AtomSymbol) = @_;
 154   my($AtomicNumber);
 155 
 156   $This->{AtomSymbol} = $AtomSymbol;
 157 
 158   $AtomicNumber = PeriodicTable::GetElementAtomicNumber($AtomSymbol);
 159   $This->{AtomicNumber} = (defined $AtomicNumber) ? $AtomicNumber : 0;
 160 
 161   return $This;
 162 }
 163 
 164 # Setup atom symbol and atomic number for the element...
 165 sub SetAtomicNumber {
 166   my($This, $AtomicNumber) = @_;
 167   my($AtomSymbol);
 168 
 169   $AtomSymbol = PeriodicTable::GetElementAtomSymbol($AtomicNumber);
 170   if (!defined $AtomSymbol) {
 171     carp "Warning: ${ClassName}->SetAtomicNumber: Didn't set atomic number: Invalid atomic number, $AtomicNumber, specified...";
 172     return;
 173   }
 174   $This->{AtomicNumber} = $AtomicNumber;
 175   $This->{AtomSymbol} = $AtomSymbol;
 176 
 177   return $This;
 178 }
 179 
 180 # Set atom as stereo center...
 181 #
 182 sub SetStereoCenter {
 183   my($This, $StereoCenter) = @_;
 184 
 185   $This->SetProperty('StereoCenter', $StereoCenter);
 186 
 187   return $This;
 188 }
 189 
 190 # Is it a stereo center?
 191 #
 192 sub IsStereoCenter {
 193   my($This) = @_;
 194   my($StereoCenter);
 195 
 196   $StereoCenter = $This->GetProperty('StereoCenter');
 197 
 198   return (defined($StereoCenter) && $StereoCenter) ? 1 : 0;
 199 }
 200 
 201 # Set atom stereochemistry.
 202 #
 203 # Supported values are: R, S.
 204 #
 205 # Notes:
 206 #
 207 # . After the ligands around a central stereocenter has been ranked using CIP priority scheme and
 208 # the lowest ranked ligand lies behind the center atom, then R and S values correspond to:
 209 #
 210 # R: Clockwise arrangement of remaining ligands around the central atom going from highest to lowest ranked ligand
 211 # S: CounterClockwise arrangement of remaining ligands around the central atom going from highest to lowest ranked ligand
 212 #
 213 # . Assignment of any other arbitray values besides R and S is also allowed; however, a warning is printed.
 214 #
 215 sub SetStereochemistry {
 216   my($This, $Stereochemistry) = @_;
 217 
 218   if ($Stereochemistry !~ /^(R|S)$/i) {
 219     carp "Warning: ${ClassName}->SetStereochemistry: Assigning non-supported Stereochemistry value of $Stereochemistry. Supported values: R, S...";
 220   }
 221 
 222   $This->SetProperty('StereoCenter', 1);
 223   $This->SetProperty('Stereochemistry', $Stereochemistry);
 224 
 225   return $This;
 226 }
 227 
 228 # Setup mass number for atom...
 229 sub SetMassNumber {
 230   my($This, $MassNumber) = @_;
 231   my($AtomicNumber, $AtomSymbol);
 232 
 233   $AtomicNumber = $This->{AtomicNumber};
 234   $AtomSymbol = $This->{AtomSymbol};
 235   if (!$AtomicNumber) {
 236     carp "Warning: ${ClassName}->SetMassNumber: Didn't set mass number: Non standard atom with atomic number, $AtomicNumber, and atomic symbol, $AtomSymbol...";
 237     return;
 238   }
 239   if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $MassNumber)) {
 240     carp "Warning: ${ClassName}->SetMassNumber: Unknown mass number, $MassNumber, specified for atom with atomic number, $AtomicNumber, and atomic symbol, $AtomSymbol. Don't forget to Set ExactMass property explicitly; otherwise, GetExactMass method would return mass of most abundant isotope...";
 241   }
 242   $This->SetProperty('MassNumber', $MassNumber);
 243 
 244   return $This;
 245 }
 246 
 247 # Get mass number...
 248 #
 249 sub GetMassNumber {
 250   my($This) = @_;
 251 
 252   # Is mass number explicity set?
 253   if ($This->HasProperty('MassNumber')) {
 254     return $This->GetProperty('MassNumber');
 255   }
 256 
 257   # Is it an element symbol?
 258   my($AtomicNumber) = $This->{AtomicNumber};
 259   if (!$AtomicNumber) {
 260     return 0;
 261   }
 262 
 263   # Return most abundant mass number...
 264   return PeriodicTable::GetElementMostAbundantNaturalIsotopeMassNumber($AtomicNumber);
 265 }
 266 
 267 # Get atomic weight:
 268 #   . Explicitly set by the caller
 269 #   . Using atomic number
 270 #
 271 sub GetAtomicWeight {
 272   my($This) = @_;
 273 
 274   # Is atomic weight explicity set?
 275   if ($This->HasProperty('AtomicWeight')) {
 276     return $This->GetProperty('AtomicWeight');
 277   }
 278 
 279   # Is it an element symbol?
 280   my($AtomicNumber) = $This->{AtomicNumber};
 281   if (!$AtomicNumber) {
 282     return 0;
 283   }
 284 
 285   # Return its atomic weight...
 286   return PeriodicTable::GetElementAtomicWeight($AtomicNumber);
 287 }
 288 
 289 # Get exact mass weight:
 290 #   . Explicitly set by the caller
 291 #   . Using atomic number and mass number explicity set by the caller
 292 #   . Using atomic number and most abundant isotope
 293 #
 294 sub GetExactMass {
 295   my($This) = @_;
 296 
 297   # Is exact mass explicity set?
 298   if ($This->HasProperty('ExactMass')) {
 299     return $This->GetProperty('ExactMass');
 300   }
 301 
 302   # Is it an element symbol?
 303   my($AtomicNumber) = $This->{AtomicNumber};
 304   if (!$AtomicNumber) {
 305     return 0;
 306   }
 307 
 308   # Is mass number explicitly set?
 309   if ($This->HasProperty('MassNumber')) {
 310     my($MassNumber) = $This->GetProperty('MassNumber');
 311     if (PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $MassNumber)) {
 312       return PeriodicTable::GetElementNaturalIsotopeMass($AtomicNumber, $MassNumber);
 313     }
 314   }
 315 
 316   # Return most abundant isotope mass...
 317   return PeriodicTable::GetElementMostAbundantNaturalIsotopeMass($AtomicNumber);
 318 }
 319 
 320 # Get formal charge:
 321 #   . Explicitly set by the caller
 322 #   . Or return zero insetad of undef
 323 #
 324 sub GetFormalCharge {
 325   my($This) = @_;
 326   my($FormalCharge);
 327 
 328   $FormalCharge = 0;
 329   if ($This->HasProperty('FormalCharge')) {
 330     $FormalCharge = $This->GetProperty('FormalCharge');
 331   }
 332 
 333   return defined($FormalCharge) ? $FormalCharge : 0;
 334 }
 335 
 336 # Get spin multiplicity:
 337 #   . Explicitly set by the caller
 338 #   . From FreeRadicalElectrons value explicitly set by the caller
 339 #   . Or return zero insetad of undef
 340 #
 341 sub GetSpinMultiplicity {
 342   my($This) = @_;
 343   my($SpinMultiplicity);
 344 
 345   $SpinMultiplicity = 0;
 346   if ($This->HasProperty('SpinMultiplicity')) {
 347     $SpinMultiplicity = $This->GetProperty('SpinMultiplicity');
 348     return defined($SpinMultiplicity) ? $SpinMultiplicity : 0;
 349   }
 350 
 351   if ($This->HasProperty('FreeRadicalElectrons')) {
 352     my($FreeRadicalElectrons);
 353     $FreeRadicalElectrons = $This->GetProperty('FreeRadicalElectrons');
 354 
 355     SPINMULTIPLICITY: {
 356       if ($FreeRadicalElectrons == 1) { $SpinMultiplicity = 2; last SPINMULTIPLICITY;}
 357       if ($FreeRadicalElectrons == 2) { $SpinMultiplicity = 1; last SPINMULTIPLICITY;}
 358       carp "Warning: ${ClassName}->GetSpinMultiplicity: It's not possible to determine spin multiplicity from the specified free radical electrons value, $FreeRadicalElectrons. It has been set to 0...";
 359       $SpinMultiplicity = 0;
 360     }
 361   }
 362 
 363   return $SpinMultiplicity;
 364 }
 365 
 366 # Get number of free radical electrons:
 367 #   . Explicitly set by the caller
 368 #   . From SpinMultiplicity value explicitly set by the caller
 369 #   . Or return zero insetad of undef
 370 #
 371 # Notes:
 372 #  . For atoms with explicit assignment of SpinMultiplicity property values corresponding to
 373 #    Singlet (two unpaired electrons corresponding to one spin state), Doublet (free radical; an unpaired
 374 #    electron corresponding to two spin states), and Triplet (two unparied electrons corresponding to
 375 #    three spin states; divalent carbon atoms (carbenes)), FreeRadicalElectrons are calculated as follows:
 376 #
 377 #       SpinMultiplicity: Doublet(2); FreeRadicalElectrons: 1
 378 #       SpinMultiplicity: Singlet(1)/Triplet(3); FreeRadicalElectrons: 2
 379 #
 380 sub GetFreeRadicalElectrons {
 381   my($This) = @_;
 382   my($FreeRadicalElectrons);
 383 
 384   $FreeRadicalElectrons = 0;
 385 
 386   if ($This->HasProperty('FreeRadicalElectrons')) {
 387     $FreeRadicalElectrons = $This->GetProperty('FreeRadicalElectrons');
 388     return defined($FreeRadicalElectrons) ? $FreeRadicalElectrons : 0;
 389   }
 390 
 391   if ($This->HasProperty('SpinMultiplicity')) {
 392     my($SpinMultiplicity);
 393     $SpinMultiplicity = $This->GetProperty('SpinMultiplicity');
 394 
 395     SPINMULTIPLICITY: {
 396       if ($SpinMultiplicity == 1) { $FreeRadicalElectrons = 2; last SPINMULTIPLICITY;}
 397       if ($SpinMultiplicity == 2) { $FreeRadicalElectrons = 1; last SPINMULTIPLICITY;}
 398       if ($SpinMultiplicity == 3) { $FreeRadicalElectrons = 2; last SPINMULTIPLICITY;}
 399       carp "Warning: ${ClassName}->GetFreeRadicalElectrons: It's not possible to determine free radical electrons from the specified spin multiplicity value, $FreeRadicalElectrons. It has been set to 0...";
 400       $FreeRadicalElectrons = 0;
 401     }
 402   }
 403 
 404   return $FreeRadicalElectrons;
 405 }
 406 
 407 # Set atom coordinates using:
 408 # . An array reference with three values
 409 # . An array containg three values
 410 # . A 3D vector
 411 #
 412 sub SetXYZ {
 413   my($This, @Values) = @_;
 414 
 415   if (!@Values) {
 416     carp "Warning: ${ClassName}->SetXYZ: No values specified...";
 417     return;
 418   }
 419 
 420   $This->{XYZ}->SetXYZ(@Values);
 421   return $This;
 422 }
 423 
 424 # Set X value...
 425 sub SetX {
 426   my($This, $Value) = @_;
 427 
 428   if (!defined $Value) {
 429     carp "Warning: ${ClassName}->SetX: Undefined X value...";
 430     return;
 431   }
 432   $This->{XYZ}->SetX($Value);
 433   return $This;
 434 }
 435 
 436 # Set Y value...
 437 sub SetY {
 438   my($This, $Value) = @_;
 439 
 440   if (!defined $Value) {
 441     carp "Warning: ${ClassName}->SetY: Undefined Y value...";
 442     return;
 443   }
 444   $This->{XYZ}->SetY($Value);
 445   return $This;
 446 }
 447 
 448 # Set Z value...
 449 sub SetZ {
 450   my($This, $Value) = @_;
 451 
 452   if (!defined $Value) {
 453     carp "Warning: ${ClassName}->SetZ: Undefined Z value...";
 454     return;
 455   }
 456   $This->{XYZ}->SetZ($Value);
 457   return $This;
 458 }
 459 
 460 # Return XYZ as:
 461 # . Reference to an array
 462 # . An array
 463 #
 464 sub GetXYZ {
 465   my($This) = @_;
 466 
 467   return $This->{XYZ}->GetXYZ();
 468 }
 469 
 470 # Return XYZ as a vector object...
 471 #
 472 sub GetXYZVector {
 473   my($This) = @_;
 474 
 475   return $This->{XYZ};
 476 }
 477 
 478 # Get X value...
 479 sub GetX {
 480   my($This) = @_;
 481 
 482   return $This->{XYZ}->GetX();
 483 }
 484 
 485 # Get Y value...
 486 sub GetY {
 487   my($This) = @_;
 488 
 489   return $This->{XYZ}->GetY();
 490 }
 491 
 492 # Get Z value...
 493 sub GetZ {
 494   my($This) = @_;
 495 
 496   return $This->{XYZ}->GetZ();
 497 }
 498 
 499 # Delete atom...
 500 sub DeleteAtom {
 501   my($This) = @_;
 502 
 503   # Is this atom in a molecule?
 504   if (!$This->HasProperty('Molecule')) {
 505     # Nothing to do...
 506     return $This;
 507   }
 508   my($Molecule) = $This->GetProperty('Molecule');
 509 
 510   return $Molecule->_DeleteAtom($This);
 511 }
 512 
 513 # Get atom neighbor objects as array. In scalar conetxt, return number of neighbors...
 514 sub GetNeighbors {
 515   my($This, @ExcludeNeighbors) = @_;
 516 
 517   # Is this atom in a molecule?
 518   if (!$This->HasProperty('Molecule')) {
 519     return undef;
 520   }
 521   my($Molecule) = $This->GetProperty('Molecule');
 522 
 523   if (@ExcludeNeighbors) {
 524     return $This->_GetAtomNeighbors(@ExcludeNeighbors);
 525   }
 526   else {
 527     return $This->_GetAtomNeighbors();
 528   }
 529 }
 530 
 531 # Get atom neighbor objects as array. In scalar conetxt, return number of neighbors...
 532 sub _GetAtomNeighbors {
 533   my($This, @ExcludeNeighbors) = @_;
 534   my($Molecule) = $This->GetProperty('Molecule');
 535 
 536   if (!@ExcludeNeighbors) {
 537     return $Molecule->_GetAtomNeighbors($This);
 538   }
 539 
 540   # Setup a map for neigbhors to exclude...
 541   my($ExcludeNeighbor, $ExcludeNeighborID, %ExcludeNeighborsIDsMap);
 542 
 543   %ExcludeNeighborsIDsMap = ();
 544   for $ExcludeNeighbor (@ExcludeNeighbors) {
 545     $ExcludeNeighborID = $ExcludeNeighbor->GetID();
 546     $ExcludeNeighborsIDsMap{$ExcludeNeighborID} = $ExcludeNeighborID;
 547   }
 548 
 549   # Generate a filtered neighbors list...
 550   my($Neighbor, $NeighborID, @FilteredAtomNeighbors);
 551   @FilteredAtomNeighbors = ();
 552   NEIGHBOR: for $Neighbor ($Molecule->_GetAtomNeighbors($This)) {
 553       $NeighborID = $Neighbor->GetID();
 554       if (exists $ExcludeNeighborsIDsMap{$NeighborID}) {
 555         next NEIGHBOR;
 556       }
 557     push @FilteredAtomNeighbors, $Neighbor;
 558   }
 559 
 560   return wantarray ? @FilteredAtomNeighbors : scalar @FilteredAtomNeighbors;
 561 }
 562 
 563 # Get specific atom neighbor objects as array. In scalar conetxt, return number of neighbors.
 564 #
 565 # Notes:
 566 #   . AtomSpecification correspond to any valid AtomicInvariant based atomic specifications
 567 #     as implemented in DoesAtomNeighborhoodMatch method.
 568 #   . Multiple atom specifications can be used in a string delimited by comma.
 569 #
 570 sub GetNeighborsUsingAtomSpecification {
 571   my($This, $AtomSpecification, @ExcludeNeighbors) = @_;
 572   my(@AtomNeighbors);
 573 
 574   @AtomNeighbors = ();
 575   @AtomNeighbors = $This->GetNeighbors(@ExcludeNeighbors);
 576 
 577   # Does atom has any neighbors and do they need to be filtered?
 578   if (!(@AtomNeighbors && defined($AtomSpecification) && $AtomSpecification)) {
 579     return wantarray ? @AtomNeighbors : scalar @AtomNeighbors;
 580   }
 581 
 582   # Filter neighbors using atom specification...
 583   my($AtomNeighbor, @FilteredAtomNeighbors);
 584 
 585   @FilteredAtomNeighbors = ();
 586   NEIGHBOR: for $AtomNeighbor (@AtomNeighbors) {
 587     if (!$AtomNeighbor->_DoesAtomSpecificationMatch($AtomSpecification)) {
 588       next NEIGHBOR;
 589     }
 590     push @FilteredAtomNeighbors, $AtomNeighbor;
 591   }
 592 
 593   return wantarray ? @FilteredAtomNeighbors : scalar @FilteredAtomNeighbors;
 594 }
 595 
 596 
 597 # Get non-hydrogen atom neighbor objects as array. In scalar context, return number of neighbors...
 598 sub GetHeavyAtomNeighbors {
 599   my($This) = @_;
 600 
 601   return $This->GetNonHydrogenAtomNeighbors();
 602 }
 603 
 604 # Get non-hydrogen atom neighbor objects as array. In scalar context, return number of neighbors...
 605 sub GetNonHydrogenAtomNeighbors {
 606   my($This) = @_;
 607 
 608   # Is this atom in a molecule?
 609   if (!$This->HasProperty('Molecule')) {
 610     return undef;
 611   }
 612   my($NonHydrogenAtomsOnly, $HydrogenAtomsOnly) = (1, 0);
 613 
 614   return $This->_GetFilteredAtomNeighbors($NonHydrogenAtomsOnly, $HydrogenAtomsOnly);
 615 }
 616 
 617 # Get hydrogen atom neighbor objects as array. In scalar context, return numbe of neighbors...
 618 sub GetHydrogenAtomNeighbors {
 619   my($This) = @_;
 620 
 621   # Is this atom in a molecule?
 622   if (!$This->HasProperty('Molecule')) {
 623     return undef;
 624   }
 625   my($NonHydrogenAtomsOnly, $HydrogenAtomsOnly) = (0, 1);
 626 
 627   return $This->_GetFilteredAtomNeighbors($NonHydrogenAtomsOnly, $HydrogenAtomsOnly);
 628 }
 629 
 630 # Get non-hydrogen neighbor of hydrogen atom...
 631 #
 632 sub GetNonHydrogenNeighborOfHydrogenAtom {
 633   my($This) = @_;
 634 
 635   # Is it Hydrogen?
 636   if (!$This->IsHydrogen()) {
 637     return undef;
 638   }
 639   my(@Neighbors);
 640 
 641   @Neighbors = $This->GetNonHydrogenAtomNeighbors();
 642 
 643   return (@Neighbors == 1) ? $Neighbors[0] : undef;
 644 }
 645 
 646 # Get filtered atom atom neighbors
 647 sub _GetFilteredAtomNeighbors {
 648   my($This, $NonHydrogenAtomsOnly, $HydrogenAtomsOnly) = @_;
 649 
 650   # Check flags...
 651   if (!defined $NonHydrogenAtomsOnly) {
 652     $NonHydrogenAtomsOnly = 0;
 653   }
 654   if (!defined $HydrogenAtomsOnly) {
 655     $HydrogenAtomsOnly = 0;
 656   }
 657   my($Neighbor, @FilteredAtomNeighbors);
 658 
 659   @FilteredAtomNeighbors = ();
 660   NEIGHBOR: for $Neighbor ($This->GetNeighbors()) {
 661     if ($NonHydrogenAtomsOnly && $Neighbor->IsHydrogen()) {
 662       next NEIGHBOR;
 663     }
 664     if ($HydrogenAtomsOnly && (!$Neighbor->IsHydrogen())) {
 665       next NEIGHBOR;
 666     }
 667     push @FilteredAtomNeighbors, $Neighbor;
 668   }
 669 
 670   return wantarray ? @FilteredAtomNeighbors : scalar @FilteredAtomNeighbors;
 671 }
 672 
 673 # Get number of neighbors...
 674 #
 675 sub GetNumOfNeighbors {
 676   my($This) = @_;
 677   my($NumOfNeighbors);
 678 
 679   $NumOfNeighbors = $This->GetNeighbors();
 680 
 681   return (defined $NumOfNeighbors) ? $NumOfNeighbors : undef;
 682 }
 683 
 684 # Get number of neighbors which are non-hydrogen atoms...
 685 sub GetNumOfHeavyAtomNeighbors {
 686   my($This) = @_;
 687 
 688   return $This->GetNumOfNonHydrogenAtomNeighbors();
 689 }
 690 
 691 # Get number of neighbors which are non-hydrogen atoms...
 692 sub GetNumOfNonHydrogenAtomNeighbors {
 693   my($This) = @_;
 694   my($NumOfNeighbors);
 695 
 696   $NumOfNeighbors = $This->GetNonHydrogenAtomNeighbors();
 697 
 698   return (defined $NumOfNeighbors) ? $NumOfNeighbors : undef;
 699 }
 700 
 701 # Get number of neighbors which are hydrogen atoms...
 702 sub GetNumOfHydrogenAtomNeighbors {
 703   my($This) = @_;
 704   my($NumOfNeighbors);
 705 
 706   $NumOfNeighbors = $This->GetHydrogenAtomNeighbors();
 707 
 708   return (defined $NumOfNeighbors) ? $NumOfNeighbors : undef;
 709 }
 710 
 711 # Get bond objects as array. In scalar context, return number of bonds...
 712 sub GetBonds {
 713   my($This) = @_;
 714 
 715   # Is this atom in a molecule?
 716   if (!$This->HasProperty('Molecule')) {
 717     return undef;
 718   }
 719   my($Molecule) = $This->GetProperty('Molecule');
 720 
 721   return $Molecule->_GetAtomBonds($This);
 722 }
 723 
 724 # Get bond to specified atom...
 725 sub GetBondToAtom {
 726   my($This, $Other) = @_;
 727 
 728   # Is this atom in a molecule?
 729   if (!$This->HasProperty('Molecule')) {
 730     return undef;
 731   }
 732   my($Molecule) = $This->GetProperty('Molecule');
 733 
 734   return $Molecule->_GetBondToAtom($This, $Other);
 735 }
 736 
 737 # It it bonded to a specified atom?
 738 sub IsBondedToAtom {
 739   my($This, $Other) = @_;
 740 
 741   # Is this atom in a molecule?
 742   if (!$This->HasProperty('Molecule')) {
 743     return undef;
 744   }
 745   my($Molecule) = $This->GetProperty('Molecule');
 746 
 747   return $Molecule->_IsBondedToAtom($This, $Other);
 748 }
 749 
 750 # Get bond objects to non-hydrogen atoms as array. In scalar context, return number of bonds...
 751 sub GetBondsToHeavyAtoms {
 752   my($This) = @_;
 753 
 754   return $This->GetBondsToNonHydrogenAtoms();
 755 }
 756 
 757 # Get bond objects to non-hydrogen atoms as array. In scalar context, return number of bonds...
 758 sub GetBondsToNonHydrogenAtoms {
 759   my($This) = @_;
 760 
 761   # Is this atom in a molecule?
 762   if (!$This->HasProperty('Molecule')) {
 763     return undef;
 764   }
 765   my($BondsToNonHydrogenAtomsOnly, $BondsToHydrogenAtomsOnly) = (1, 0);
 766 
 767   return $This->_GetFilteredBonds($BondsToNonHydrogenAtomsOnly, $BondsToHydrogenAtomsOnly);
 768 }
 769 
 770 # Get bond objects to hydrogen atoms as array. In scalar context, return number of bonds...
 771 sub GetBondsToHydrogenAtoms {
 772   my($This) = @_;
 773 
 774   # Is this atom in a molecule?
 775   if (!$This->HasProperty('Molecule')) {
 776     return undef;
 777   }
 778   my($BondsToNonHydrogenAtomsOnly, $BondsToHydrogenAtomsOnly) = (0, 1);
 779 
 780   return $This->_GetFilteredBonds($BondsToNonHydrogenAtomsOnly, $BondsToHydrogenAtomsOnly);
 781 }
 782 
 783 # Get filtered bonds...
 784 sub _GetFilteredBonds {
 785   my($This, $BondsToNonHydrogenAtomsOnly, $BondsToHydrogenAtomsOnly) = @_;
 786 
 787   # Check flags...
 788   if (!defined $BondsToNonHydrogenAtomsOnly) {
 789     $BondsToNonHydrogenAtomsOnly = 0;
 790   }
 791   if (!defined $BondsToHydrogenAtomsOnly) {
 792     $BondsToHydrogenAtomsOnly = 0;
 793   }
 794 
 795   my($Bond, $BondedAtom, @FilteredBonds);
 796 
 797   @FilteredBonds = ();
 798   BOND: for $Bond ($This->GetBonds()) {
 799     $BondedAtom = $Bond->GetBondedAtom($This);
 800     if ($BondsToNonHydrogenAtomsOnly && $BondedAtom->IsHydrogen()) {
 801       next BOND;
 802     }
 803     if ($BondsToHydrogenAtomsOnly && (!$BondedAtom->IsHydrogen())) {
 804       next BOND;
 805     }
 806     push @FilteredBonds, $Bond;
 807   }
 808 
 809   return wantarray ? @FilteredBonds : (scalar @FilteredBonds);
 810 }
 811 
 812 # Get number of bonds...
 813 #
 814 sub GetNumOfBonds {
 815   my($This) = @_;
 816   my($NumOfBonds);
 817 
 818   $NumOfBonds = $This->GetBonds();
 819 
 820   return (defined $NumOfBonds) ? ($NumOfBonds) : undef;
 821 }
 822 
 823 # Get number of bonds to non-hydrogen atoms...
 824 sub GetNumOfBondsToHeavyAtoms {
 825   my($This) = @_;
 826 
 827   return $This->GetNumOfBondsToNonHydrogenAtoms();
 828 }
 829 
 830 # Get number of bonds to non-hydrogen atoms...
 831 sub GetNumOfBondsToNonHydrogenAtoms {
 832   my($This) = @_;
 833   my($NumOfBonds);
 834 
 835   $NumOfBonds = $This->GetBondsToNonHydrogenAtoms();
 836 
 837   return (defined $NumOfBonds) ? ($NumOfBonds) : undef;
 838 }
 839 
 840 # Get number of single bonds to heavy atoms...
 841 sub GetNumOfSingleBondsToHeavyAtoms {
 842   my($This) = @_;
 843 
 844   return $This->GetNumOfSingleBondsToNonHydrogenAtoms();
 845 }
 846 
 847 # Get number of single bonds to non-hydrogen atoms...
 848 sub GetNumOfSingleBondsToNonHydrogenAtoms {
 849   my($This) = @_;
 850 
 851   # Is this atom in a molecule?
 852   if (!$This->HasProperty('Molecule')) {
 853     return undef;
 854   }
 855   return $This->_GetNumOfBondsWithSpecifiedBondOrderToNonHydrogenAtoms(1);
 856 }
 857 
 858 # Get number of double bonds to heavy atoms...
 859 sub GetNumOfDoubleBondsToHeavyAtoms {
 860   my($This) = @_;
 861 
 862   return $This->GetNumOfDoubleBondsToNonHydrogenAtoms();
 863 }
 864 
 865 # Get number of double bonds to non-hydrogen atoms...
 866 sub GetNumOfDoubleBondsToNonHydrogenAtoms {
 867   my($This) = @_;
 868 
 869   # Is this atom in a molecule?
 870   if (!$This->HasProperty('Molecule')) {
 871     return undef;
 872   }
 873   return $This->_GetNumOfBondsWithSpecifiedBondOrderToNonHydrogenAtoms(2);
 874 }
 875 
 876 # Get number of triple bonds to heavy atoms...
 877 sub GetNumOfTripleBondsToHeavyAtoms {
 878   my($This) = @_;
 879 
 880   return $This->GetNumOfTripleBondsToNonHydrogenAtoms();
 881 }
 882 
 883 # Get number of triple bonds to non-hydrogen atoms...
 884 sub GetNumOfTripleBondsToNonHydrogenAtoms {
 885   my($This) = @_;
 886 
 887   # Is this atom in a molecule?
 888   if (!$This->HasProperty('Molecule')) {
 889     return undef;
 890   }
 891   return $This->_GetNumOfBondsWithSpecifiedBondOrderToNonHydrogenAtoms(3);
 892 }
 893 
 894 # Get number of bonds of specified bond order to non-hydrogen atoms...
 895 sub _GetNumOfBondsWithSpecifiedBondOrderToNonHydrogenAtoms {
 896   my($This, $SpecifiedBondOrder) = @_;
 897   my($NumOfBonds, $Bond, $BondOrder, @Bonds);
 898 
 899   $NumOfBonds = 0;
 900   @Bonds = $This->GetBondsToNonHydrogenAtoms();
 901   for $Bond (@Bonds) {
 902     $BondOrder = $Bond->GetBondOrder();
 903     if ($SpecifiedBondOrder == $BondOrder) {
 904       $NumOfBonds++;
 905     }
 906   }
 907   return $NumOfBonds;
 908 }
 909 
 910 # Get number of aromatic bonds to heavy atoms...
 911 sub GetNumOfAromaticBondsToHeavyAtoms {
 912   my($This) = @_;
 913 
 914   return $This->GetNumOfAromaticBondsToNonHydrogenAtoms();
 915 }
 916 
 917 # Get number of aromatic bonds to non-hydrogen atoms...
 918 sub GetNumOfAromaticBondsToNonHydrogenAtoms {
 919   my($This) = @_;
 920   my($NumOfBonds, $Bond, @Bonds);
 921 
 922   # Is this atom in a molecule?
 923   if (!$This->HasProperty('Molecule')) {
 924     return undef;
 925   }
 926 
 927   $NumOfBonds = 0;
 928   @Bonds = $This->GetBondsToNonHydrogenAtoms();
 929   for $Bond (@Bonds) {
 930     if ($Bond->IsAromatic()) { $NumOfBonds++; }
 931   }
 932   return $NumOfBonds;
 933 }
 934 
 935 # Get number of different bond types to non-hydrogen atoms...
 936 #
 937 sub GetNumOfBondTypesToHeavyAtoms {
 938   my($This, $CountAromaticBonds) = @_;
 939 
 940   return $This->GetNumOfBondTypesToNonHydrogenAtoms($CountAromaticBonds);
 941 }
 942 
 943 # Get number of single, double, triple, and aromatic bonds from an atom to all other
 944 # non-hydrogen atoms. Value of CountAtomaticBonds parameter controls whether
 945 # number of aromatic bonds is returned; default is not to count aromatic bonds. During
 946 # counting of aromatic bonds, the bond marked aromatic is not included in the count
 947 # of other bond types.
 948 #
 949 sub GetNumOfBondTypesToNonHydrogenAtoms {
 950   my($This, $CountAromaticBonds) = @_;
 951   my($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfAromaticBonds, $None, $Bond, @Bonds);
 952 
 953   $CountAromaticBonds = defined($CountAromaticBonds) ? $CountAromaticBonds : 0;
 954 
 955   ($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds) = ('0') x 3;
 956   $NumOfAromaticBonds = $CountAromaticBonds ? 0 : undef;
 957 
 958   # Is this atom in a molecule?
 959   if (!$This->HasProperty('Molecule')) {
 960     return ($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfAromaticBonds);
 961   }
 962 
 963   @Bonds = $This->GetBondsToNonHydrogenAtoms();
 964 
 965   for $Bond (@Bonds) {
 966     BONDTYPE: {
 967       if ($CountAromaticBonds) {
 968         if ($Bond->IsAromatic()) { $NumOfAromaticBonds++; last BONDTYPE; }
 969       }
 970       if ($Bond->IsSingle()) { $NumOfSingleBonds++; last BONDTYPE; }
 971       if ($Bond->IsDouble()) { $NumOfDoubleBonds++; last BONDTYPE; }
 972       if ($Bond->IsTriple()) { $NumOfTripleBonds++; last BONDTYPE; }
 973       $None = 1;
 974     }
 975   }
 976   return ($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfAromaticBonds);
 977 }
 978 
 979 # Get number of sigma and pi bonds to heavy atoms...
 980 #
 981 sub GetNumOfSigmaAndPiBondsToHeavyAtoms {
 982   my($This) = @_;
 983 
 984   return $This->GetNumOfSigmaAndPiBondsToNonHydrogenAtoms();
 985 }
 986 
 987 # Get number of sigma and pi bonds from an atom to all other non-hydrogen atoms.
 988 # Sigma and pi bonds are counted using the following methodology: a single bond
 989 # correspond to one sigma bond; a double bond contributes one to sigma bond count
 990 # and one to pi bond count; a triple bond contributes one to sigma bond count and
 991 # two to pi bond count.
 992 #
 993 sub GetNumOfSigmaAndPiBondsToNonHydrogenAtoms {
 994   my($This) = @_;
 995   my($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfSigmaBonds, $NumOfPiBonds);
 996 
 997   ($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds) = $This->GetNumOfBondTypesToNonHydrogenAtoms();
 998 
 999   $NumOfSigmaBonds = $NumOfSingleBonds + $NumOfDoubleBonds + $NumOfTripleBonds;
1000   $NumOfPiBonds = $NumOfDoubleBonds + 2*$NumOfTripleBonds;
1001 
1002   return ($NumOfSigmaBonds, $NumOfPiBonds);
1003 }
1004 
1005 # Get information related to atoms for all heavy atoms attached to an atom..
1006 #
1007 sub GetHeavyAtomNeighborsAtomInformation {
1008   my($This) = @_;
1009 
1010   return $This->GetNonHydrogenAtomNeighborsAtomInformation();
1011 }
1012 
1013 # Get information related to atoms for all non-hydrogen atoms attached to an atom..
1014 #
1015 # The following values are returned:
1016 #   . Number of non-hydrogen atom neighbors
1017 #   . A reference to an array containing atom objects correpsonding to non-hydrogen
1018 #     atom neighbors
1019 #   . Number of different types of non-hydrogen atom neighbors
1020 #   . A reference to a hash containing atom symbol as key with value corresponding
1021 #     to its count for non-hydrogen atom neighbors
1022 #
1023 sub GetNonHydrogenAtomNeighborsAtomInformation {
1024   my($This) = @_;
1025 
1026   # Is this atom in a molecule?
1027   if (!$This->HasProperty('Molecule')) {
1028     return (undef, undef, undef, undef);
1029   }
1030   my($AtomSymbol, $AtomNeighbor, $NumOfAtomNeighbors, $NumOfAtomNeighborsType, @AtomNeighbors, %AtomNeighborsTypeMap);
1031 
1032   $NumOfAtomNeighbors = 0; @AtomNeighbors = ();
1033   $NumOfAtomNeighborsType = 0; %AtomNeighborsTypeMap = ();
1034 
1035   @AtomNeighbors = $This->GetNonHydrogenAtomNeighbors();
1036   $NumOfAtomNeighbors = scalar @AtomNeighbors;
1037 
1038   for $AtomNeighbor (@AtomNeighbors) {
1039     $AtomSymbol = $AtomNeighbor->{AtomSymbol};
1040     if (exists $AtomNeighborsTypeMap{$AtomSymbol}) {
1041       $AtomNeighborsTypeMap{$AtomSymbol} += 1;
1042     }
1043     else {
1044       $AtomNeighborsTypeMap{$AtomSymbol} = 1;
1045       $NumOfAtomNeighborsType++;
1046     }
1047   }
1048 
1049   return ($NumOfAtomNeighbors, \@AtomNeighbors, $NumOfAtomNeighborsType, \%AtomNeighborsTypeMap);
1050 }
1051 
1052 # Get information related to bonds for all heavy atoms attached to an atom..
1053 #
1054 sub GetHeavyAtomNeighborsBondformation {
1055   my($This) = @_;
1056 
1057   return $This->GetNonHydrogenAtomNeighborsBondInformation();
1058 }
1059 
1060 # Get information related to bonds for all non-hydrogen atoms attached to an atom..
1061 #
1062 # The following values are returned:
1063 #   . Number of bonds to non-hydrogen atom neighbors
1064 #   . A reference to an array containing bond objects correpsonding to non-hydrogen
1065 #     atom neighbors
1066 #   . A reference to a hash containing bond type as key with value corresponding
1067 #     to its count for non-hydrogen atom neighbors. Bond types are: Single, Double or Triple
1068 #   . A reference to a hash containing atom symbol as key pointing to bond type as second
1069 #     key with values correponding to count of bond types for atom symbol for non-hydrogen
1070 #     atom neighbors
1071 #   . A reference to a hash containing atom symbol as key pointing to bond type as second
1072 #     key with values correponding to atom objects array involved in corresponding bond type for
1073 #     atom symbol for non-hydrogen atom neighbors
1074 #
1075 sub GetNonHydrogenAtomNeighborsBondInformation {
1076   my($This) = @_;
1077 
1078   # Is this atom in a molecule?
1079   if (!$This->HasProperty('Molecule')) {
1080     return (undef, undef, undef, undef, undef);
1081   }
1082   my($BondedAtom, $BondedAtomSymbol, $BondType, $None, $Bond, $NumOfBonds, @Bonds, %BondTypeCountMap, %AtomsBondTypesCountMap, %AtomsBondTypeAtomsMap);
1083 
1084   $NumOfBonds = 0; @Bonds = ();
1085   %BondTypeCountMap = ();
1086   %AtomsBondTypesCountMap = (); %AtomsBondTypeAtomsMap = ();
1087 
1088   $BondTypeCountMap{Single} = 0;
1089   $BondTypeCountMap{Double} = 0;
1090   $BondTypeCountMap{Triple} = 0;
1091 
1092   @Bonds = $This->GetBondsToNonHydrogenAtoms();
1093   $NumOfBonds = scalar @Bonds;
1094 
1095   BOND: for $Bond (@Bonds) {
1096     $BondType = $Bond->IsSingle() ? "Single" : ($Bond->IsDouble() ? "Double" : ($Bond->IsTriple() ? "Triple" : ""));
1097     if (!$BondType) {
1098       next BOND;
1099     }
1100 
1101     # Track bond types...
1102     if (exists $BondTypeCountMap{$BondType}) {
1103       $BondTypeCountMap{$BondType} += 1;
1104     }
1105     else {
1106       $BondTypeCountMap{$BondType} = 1;
1107     }
1108 
1109     $BondedAtom = $Bond->GetBondedAtom($This);
1110     $BondedAtomSymbol = $BondedAtom->{AtomSymbol};
1111 
1112     # Track bond types count for atom types involved in specific bond types...
1113     if (!exists $AtomsBondTypesCountMap{$BondedAtomSymbol}) {
1114       %{$AtomsBondTypesCountMap{$BondedAtomSymbol}} = ();
1115     }
1116     if (exists $AtomsBondTypesCountMap{$BondedAtomSymbol}{$BondType}) {
1117       $AtomsBondTypesCountMap{$BondedAtomSymbol}{$BondType} += 1;
1118     }
1119     else {
1120       $AtomsBondTypesCountMap{$BondedAtomSymbol}{$BondType} = 1;
1121     }
1122 
1123     # Track atoms involved in specific bond types for specific atom types...
1124     if (!exists $AtomsBondTypeAtomsMap{$BondedAtomSymbol}) {
1125       %{$AtomsBondTypeAtomsMap{$BondedAtomSymbol}} = ();
1126     }
1127     if (!exists $AtomsBondTypeAtomsMap{$BondedAtomSymbol}{$BondType}) {
1128       @{$AtomsBondTypeAtomsMap{$BondedAtomSymbol}{$BondType}} = ();
1129     }
1130     push @{$AtomsBondTypeAtomsMap{$BondedAtomSymbol}{$BondType}}, $BondedAtom;
1131   }
1132 
1133   return ($NumOfBonds, \@Bonds, \%BondTypeCountMap, \%AtomsBondTypesCountMap, \%AtomsBondTypeAtomsMap);
1134 }
1135 
1136 # Get number of bonds to hydrogen atoms...
1137 sub GetNumOfBondsToHydrogenAtoms {
1138   my($This) = @_;
1139   my($NumOfBonds);
1140 
1141   $NumOfBonds = $This->GetBondsToHydrogenAtoms();
1142 
1143   return (defined $NumOfBonds) ? ($NumOfBonds) : undef;
1144 }
1145 
1146 # Get sum of bond orders to all bonded atoms...
1147 #
1148 sub GetSumOfBondOrders {
1149   my($This) = @_;
1150 
1151   # Is this atom in a molecule?
1152   if (!$This->HasProperty('Molecule')) {
1153     return undef;
1154   }
1155 
1156   return $This->_GetSumOfBondOrders();
1157 }
1158 
1159 # Get sum of bond orders to non-hydrogen atoms only...
1160 #
1161 sub GetSumOfBondOrdersToHeavyAtoms {
1162   my($This) = @_;
1163 
1164   return $This->GetSumOfBondOrdersToNonHydrogenAtoms();
1165 }
1166 
1167 # Get sum of bond orders to non-hydrogen atoms only...
1168 #
1169 sub GetSumOfBondOrdersToNonHydrogenAtoms {
1170   my($This) = @_;
1171 
1172   # Is this atom in a molecule?
1173   if (!$This->HasProperty('Molecule')) {
1174     return undef;
1175   }
1176   my($ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly) = (1, 0);
1177 
1178   return $This->_GetSumOfBondOrders($ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly);
1179 }
1180 
1181 # Get sum of bond orders to hydrogen atoms only...
1182 #
1183 sub GetSumOfBondOrdersToHydrogenAtoms {
1184   my($This) = @_;
1185 
1186   # Is this atom in a molecule?
1187   if (!$This->HasProperty('Molecule')) {
1188     return undef;
1189   }
1190   my($ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly) = (0, 1);
1191 
1192   return $This->_GetSumOfBondOrders($ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly);
1193 }
1194 
1195 # Get sum of bond orders to all bonded atoms,  non-hydrogen or hydrogen bonded atoms...
1196 #
1197 sub _GetSumOfBondOrders {
1198   my($This, $ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly) = @_;
1199 
1200   # Check flags...
1201   if (!defined $ToNonHydrogenAtomsOnly) {
1202     $ToNonHydrogenAtomsOnly = 0;
1203   }
1204   if (!defined $ToHydrogenAtomsOnly) {
1205     $ToHydrogenAtomsOnly = 0;
1206   }
1207   my($Bond, $SumOfBondOrders, @Bonds);
1208   @Bonds = ();
1209 
1210   if ($ToNonHydrogenAtomsOnly) {
1211     @Bonds = $This->GetBondsToNonHydrogenAtoms();
1212   }
1213   elsif ($ToHydrogenAtomsOnly) {
1214     @Bonds = $This->GetBondsToHydrogenAtoms();
1215   }
1216   else {
1217     # All bonds...
1218     @Bonds = $This->GetBonds();
1219   }
1220 
1221   $SumOfBondOrders = 0;
1222   for $Bond (@Bonds) {
1223     $SumOfBondOrders += $Bond->GetBondOrder();
1224   }
1225 
1226   if ($SumOfBondOrders =~ /\./) {
1227     #
1228     # Change any fractional bond order to next largest integer...
1229     #
1230     # As long as aromatic bond orders in a ring are correctly using using 4n + 2 Huckel rule
1231     # (BondOrder: 1.5) or explicity set as Kekule  bonds (alternate single/double),
1232     # SumOfBondOrders should add up to an integer.
1233     #
1234     $SumOfBondOrders = ceil($SumOfBondOrders);
1235   }
1236 
1237   return $SumOfBondOrders;
1238 }
1239 
1240 # Get largest bond order to any bonded atoms...
1241 #
1242 sub GetLargestBondOrder {
1243   my($This) = @_;
1244 
1245   # Is this atom in a molecule?
1246   if (!$This->HasProperty('Molecule')) {
1247     return undef;
1248   }
1249 
1250   return $This->_GetLargestBondOrder();
1251 }
1252 
1253 # Get largest bond order to bonded non-hydrogen atoms...
1254 #
1255 sub GetLargestBondOrderToHeavyAtoms {
1256   my($This) = @_;
1257 
1258   return $This->GetLargestBondOrderToNonHydrogenAtoms();
1259 }
1260 
1261 # Get largest bond order to bonded non-hydrogen atoms...
1262 #
1263 sub GetLargestBondOrderToNonHydrogenAtoms {
1264   my($This) = @_;
1265 
1266   # Is this atom in a molecule?
1267   if (!$This->HasProperty('Molecule')) {
1268     return undef;
1269   }
1270 
1271   my($ToNonHydrogenAtomsOnly) = (1);
1272 
1273   return $This->_GetLargestBondOrder($ToNonHydrogenAtomsOnly);
1274 }
1275 
1276 # Get largest bond order to all bonded atoms, non-hydrogen or hydrogen bonded atoms...
1277 #
1278 sub _GetLargestBondOrder {
1279   my($This, $ToNonHydrogenAtomsOnly, $ToHydrogenAtomsOnly) = @_;
1280 
1281   # Check flags...
1282   if (!defined $ToNonHydrogenAtomsOnly) {
1283     $ToNonHydrogenAtomsOnly = 0;
1284   }
1285   if (!defined $ToHydrogenAtomsOnly) {
1286     $ToHydrogenAtomsOnly = 0;
1287   }
1288   my($Bond, $LargestBondOrder, $BondOrder, @Bonds);
1289   @Bonds = ();
1290 
1291   if ($ToNonHydrogenAtomsOnly) {
1292     @Bonds = $This->GetBondsToNonHydrogenAtoms();
1293   }
1294   elsif ($ToHydrogenAtomsOnly) {
1295     @Bonds = $This->GetBondsToHydrogenAtoms();
1296   }
1297   else {
1298     # All bonds...
1299     @Bonds = $This->GetBonds();
1300   }
1301 
1302   $LargestBondOrder = 0;
1303   for $Bond (@Bonds) {
1304     $BondOrder = $Bond->GetBondOrder();
1305     if ($BondOrder > $LargestBondOrder) {
1306       $LargestBondOrder = $BondOrder;
1307     }
1308   }
1309 
1310   return $LargestBondOrder;
1311 }
1312 
1313 # Get number of implicit hydrogen for atom...
1314 #
1315 sub GetImplicitHydrogens {
1316   my($This) = @_;
1317 
1318   return $This->GetNumOfImplicitHydrogens();
1319 }
1320 
1321 # Get number of implicit hydrogen for atom...
1322 #
1323 sub GetNumOfImplicitHydrogens {
1324   my($This) = @_;
1325 
1326   # Is this atom in a molecule?
1327   if (!$This->HasProperty('Molecule')) {
1328     return undef;
1329   }
1330 
1331   # Is ImplicitHydrogens property explicitly set?
1332   if ($This->HasProperty('ImplicitHydrogens')) {
1333     return $This->GetProperty('ImplicitHydrogens');
1334   }
1335 
1336   # Is it an element symbol?
1337   if (!$This->{AtomicNumber}) {
1338     return 0;
1339   }
1340 
1341   my($ImplicitHydrogens, $PotentialTotalValence, $SumOfBondOrders);
1342 
1343   $ImplicitHydrogens = 0;
1344   $SumOfBondOrders = $This->GetSumOfBondOrders();
1345   $PotentialTotalValence = $This->GetPotentialTotalCommonValence();
1346 
1347   if (defined($PotentialTotalValence) && defined($SumOfBondOrders)) {
1348     # Subtract sum of bond orders to non-hydrogen and hydrogen atom neighbors...
1349     $ImplicitHydrogens = $PotentialTotalValence - $SumOfBondOrders;
1350   }
1351 
1352   return $ImplicitHydrogens > 0 ? $ImplicitHydrogens : 0;
1353 }
1354 
1355 # Get number of bonds available to form additional bonds with heavy atoms, excluding
1356 # any implicit bonds to hydrogens set using ImplicitHydrogens property.
1357 #
1358 # It's different from number of implicit or missing hydrogens, both of which are equivalent.
1359 #
1360 # For example, in a SMILES string, [nH] ring atom corresponds to an aromatic nitrogen.
1361 # Although the hydrogen specified for n is treated internally as implicit hydrogen and shows
1362 # up in missing hydrogen count, it's not available to participate in double bonds to additional
1363 # heavy atoms.
1364 #
1365 sub GetNumOfBondsAvailableForHeavyAtoms {
1366   my($This) = @_;
1367 
1368   return $This->GetNumOfBondsAvailableForNonHydrogenAtoms();
1369 }
1370 
1371 # It's another name for GetNumOfBondsAvailableForHeavyAtoms
1372 #
1373 sub GetNumOfBondsAvailableForNonHydrogenAtoms {
1374   my($This) = @_;
1375   my($NumOfAvailableBonds, $PotentialTotalValence, $SumOfBondOrders);
1376 
1377   $NumOfAvailableBonds = 0;
1378 
1379   $SumOfBondOrders = $This->GetSumOfBondOrders();
1380   $PotentialTotalValence = $This->GetPotentialTotalCommonValence();
1381 
1382   if (defined($PotentialTotalValence) && defined($SumOfBondOrders)) {
1383     # Subtract sum of bond orders to non-hydrogen and hydrogen atom neighbors...
1384     $NumOfAvailableBonds = $PotentialTotalValence - $SumOfBondOrders;
1385   }
1386 
1387   if ($This->HasProperty('ImplicitHydrogens')) {
1388     $NumOfAvailableBonds -= $This->GetProperty('ImplicitHydrogens');
1389   }
1390 
1391   return $NumOfAvailableBonds > 0 ? $NumOfAvailableBonds : 0;
1392 }
1393 
1394 # Disable setting of explicit hydrogens property...
1395 sub SetExplicitHydrogens {
1396   my($This, $Value) = @_;
1397 
1398   carp "Warning: ${ClassName}->SetExplicitHydrogens: Setting of explicit hydrogens is not supported...";
1399 
1400   return $This;
1401 }
1402 
1403 # Get number of explicit hydrogens for atom...
1404 #
1405 sub GetExplicitHydrogens {
1406   my($This) = @_;
1407 
1408   return $This->GetNumOfExplicitHydrogens();
1409 }
1410 
1411 # Get number of explicit hydrogens for atom...
1412 #
1413 sub GetNumOfExplicitHydrogens {
1414   my($This) = @_;
1415   my($HydrogenAtomNbrs);
1416 
1417   # Is this atom in a molecule?
1418   if (!$This->HasProperty('Molecule')) {
1419     return undef;
1420   }
1421 
1422   $HydrogenAtomNbrs = $This->GetNumOfHydrogenAtomNeighbors();
1423 
1424   return defined $HydrogenAtomNbrs ? $HydrogenAtomNbrs : 0;
1425 }
1426 
1427 # Get num of missing hydrogens...
1428 #
1429 sub GetMissingHydrogens {
1430   my($This) = @_;
1431 
1432   return $This->GetNumOfMissingHydrogens();
1433 }
1434 
1435 # Get num of missing hydrogens...
1436 #
1437 sub GetNumOfMissingHydrogens {
1438   my($This) = @_;
1439 
1440   # Is this atom in a molecule?
1441   if (!$This->HasProperty('Molecule')) {
1442     return undef;
1443   }
1444 
1445   return $This->GetNumOfImplicitHydrogens();
1446 }
1447 
1448 # Get total number of hydrogens...
1449 #
1450 sub GetHydrogens {
1451   my($This) = @_;
1452 
1453   return $This->GetNumOfHydrogens();
1454 }
1455 
1456 # Get total number of hydrogens...
1457 #
1458 sub GetNumOfHydrogens {
1459   my($This) = @_;
1460 
1461   # Is this atom in a molecule?
1462   if (!$This->HasProperty('Molecule')) {
1463     return undef;
1464   }
1465 
1466   return $This->GetNumOfImplicitHydrogens() + $This->GetNumOfExplicitHydrogens();
1467 }
1468 
1469 # Valence corresponds to the number of electrons used by an atom in bonding:
1470 #
1471 #   Valence = ValenceElectrons - ValenceFreeElectrons = BondingElectrons
1472 #
1473 # Single, double, triple bonds with bond orders of 1, 2 and 3 correspond to contribution of
1474 # 1, 2, and 3 bonding electrons. So Valence can be computed using:
1475 #
1476 #   Valence = SumOfBondOrders + NumOfMissingHydrogens + FormalCharge
1477 #
1478 # where positive and negative values of FormalCharge increase and decrease the number
1479 # of bonding electrons respectively.
1480 #
1481 # The current release of MayaChemTools supports the following three valence models, which
1482 # are used during calculation of implicit hydrogens: MDLValenceModel, DaylightValenceModel,
1483 # InternalValenceModel or MayaChemToolsValenceModel.
1484 #
1485 # Notes:
1486 #  . This doesn't always corresponds to explicit valence.
1487 #  . Missing hydrogens are included in the valence.
1488 #  . For neutral molecules, valence and sum of bond order are equal.
1489 #  . For molecules containing only single bonds, SumOfBondOrders and NumOfBonds are equal.
1490 #  . Free radical electrons lead to the decrease in valence. For atoms with explicit assignment
1491 #    of SpinMultiplicity property values corresponding to Singlet (two unparied electrons
1492 #    corresponding to one spin state), Doublet (free radical; an unpaired electron corresponding
1493 #    to two spin states), and Triplet (two unparied electrons corresponding to three spin states;
1494 #    divalent carbon atoms (carbenes)), FreeRadicalElectrons are calculated as follows:
1495 #
1496 #       SpinMultiplicity: Doublet(2); FreeRadicalElectrons: 1 (one valence electron not available for bonding)
1497 #       SpinMultiplicity: Singlet(1)/Triplet(3); FreeRadicalElectrons: 2 (two valence electrons not available for bonding)
1498 #
1499 sub GetValence {
1500   my($This) = @_;
1501 
1502   # Is this atom in a molecule?
1503   if (!$This->HasProperty('Molecule')) {
1504     return undef;
1505   }
1506 
1507   # Is Valence property explicitly set?
1508   if ($This->HasProperty('Valence')) {
1509     return $This->GetProperty('Valence');
1510   }
1511   my($Valence);
1512 
1513   $Valence = $This->GetSumOfBondOrders() + $This->GetNumOfMissingHydrogens() + $This->GetFormalCharge();
1514 
1515   return $Valence > 0 ? $Valence : 0;
1516 }
1517 
1518 # Get free non-bodning valence electrons left on atom after taking into account
1519 # sum of bond orders, missing hydrogens and formal charged on the atom. Free
1520 # radical electrons are included in the valence free electrons count by default.
1521 #
1522 # Valence corresponds to number of electrons used by atom in bonding:
1523 #
1524 #   Valence = ValenceElectrons - ValenceFreeElectrons
1525 #
1526 # Additionally, valence can also be calculated by:
1527 #
1528 #   Valence = SumOfBondOrders + NumOfMissingHydrogens + FormalCharge
1529 #
1530 # Valence and SumOfBondOrders are equal for neutral molecules.
1531 #
1532 # From two formulas for Valence described above, non-bonding free electrons
1533 # left can be computed by:
1534 #
1535 #  ValenceFreeElectrons = ValenceElectrons - Valence
1536 #                       = ValenceElectrons - SumOfBondOrders -
1537 #                         NumOfMissingHydrogens - FormalCharge
1538 #
1539 # . Notes:
1540 #    . Missing hydrogens are excluded from the valence free electrons.
1541 #    . Any free radical electrons are considered part of the valence free electrons
1542 #      by default.
1543 #
1544 # Examples:
1545 #
1546 # o NH3: ValenceFreeElectrons = 5 - 3 = 5 - 3 - 0 - 0 = 2
1547 # o NH2: ValenceFreeElectrons = 5 - 3 = 5 - 2 - 1 - 0 = 2
1548 # o NH4+; ValenceFreeElectrons = 5 - 5 = 5 - 4 - 0 - 1 = 0
1549 # o NH3+; ValenceFreeElectrons = 5 - 5 = 5 - 3 - 1 - 1 = 0
1550 # o C(=O)O- : ValenceFreeElectrons on O- = 6 - 0 = 6 - 1 - 0 - (-1) = 6
1551 # o C(=O)O- : ValenceFreeElectrons on =O = 6 - 2 = 6 - 2 - 0 - 0 = 4
1552 #
1553 #
1554 sub GetValenceFreeElectrons {
1555   my($This, $ExcludeFreeRadicalElectrons) = @_;
1556 
1557   # Is this atom in a molecule?
1558   if (!$This->HasProperty('Molecule')) {
1559     return undef;
1560   }
1561 
1562   # Is ValenceFreeElectrons property explicitly set?
1563   if ($This->HasProperty('ValenceFreeElectrons')) {
1564     return $This->GetProperty('ValenceFreeElectrons');
1565   }
1566 
1567   if (!$This->{AtomicNumber}) {
1568     return 0;
1569   }
1570 
1571   my($ValenceFreeElectrons);
1572 
1573   $ValenceFreeElectrons = $This->GetValenceElectrons() - $This->GetValence();
1574   if ($ExcludeFreeRadicalElectrons) {
1575     $ValenceFreeElectrons -= $This->GetFreeRadicalElectrons();
1576   }
1577 
1578   return $ValenceFreeElectrons > 0 ? $ValenceFreeElectrons : 0;
1579 }
1580 
1581 # Get potential total common valence for calculating the number of implicit hydrogens
1582 # using the specified common valence model or default internal model for a molecule...
1583 #
1584 sub GetPotentialTotalCommonValence {
1585   my($This) = @_;
1586 
1587   # Is this atom in a molecule?
1588   if (!$This->HasProperty('Molecule')) {
1589     return undef;
1590   }
1591   my($PotentialTotalValence, $ValenceModel);
1592 
1593   $PotentialTotalValence = 0;
1594   $ValenceModel = $This->GetProperty('Molecule')->GetValenceModel();
1595 
1596   VALENCEMODEL: {
1597     if ($ValenceModel =~ /^MDLValenceModel$/i) {
1598       $PotentialTotalValence = $This->_GetPotentialTotalCommonValenceUsingMDLValenceModel();
1599       last VALENCEMODEL;
1600     }
1601     if ($ValenceModel =~ /^DaylightValenceModel$/i) {
1602        $PotentialTotalValence = $This->_GetPotentialTotalCommonValenceUsingDaylightValenceModel();
1603       last VALENCEMODEL;
1604     }
1605     if ($ValenceModel !~ /^(InternalValenceModel|MayaChemToolsValenceModel)$/i) {
1606       carp "Warning: ${ClassName}->GetPotentialTotalCommonValence: The current release of MayaChemTools doesn't support the specified valence model $ValenceModel. Supported valence models: MDLValenceModel, DaylightValenceModel, InternalValenceModel. Using internal valence model...";
1607     }
1608     # Use internal valence model as the default valence model...
1609     $PotentialTotalValence = $This->_GetPotentialTotalCommonValenceUsingInternalValenceModel();
1610   }
1611 
1612   return $PotentialTotalValence;
1613 }
1614 
1615 # Get potential total common valence using data for MDL valence model available in file,
1616 # lib/data/MDLValenceModelData.csv, distributed with the package...
1617 #
1618 sub _GetPotentialTotalCommonValenceUsingMDLValenceModel {
1619   my($This) = @_;
1620 
1621   return $This->_GetPotentialTotalCommonValenceUsingValenceModelData(\%MDLValenceModelDataMap);
1622 
1623 }
1624 
1625 # Get potential total common valence using data for Daylight valence model available in file,
1626 # lib/data/DaylightValenceModelData.csv, distributed with the release...
1627 #
1628 sub _GetPotentialTotalCommonValenceUsingDaylightValenceModel {
1629   my($This) = @_;
1630 
1631   return $This->_GetPotentialTotalCommonValenceUsingValenceModelData(\%DaylightValenceModelDataMap);
1632 }
1633 
1634 # Get potential total common valence using data for a specific valence model...
1635 #
1636 sub _GetPotentialTotalCommonValenceUsingValenceModelData {
1637   my($This, $ValenceModelDataRef) = @_;
1638   my($AtomicNumber, $FormalCharge);
1639 
1640   $AtomicNumber = $This->{AtomicNumber};
1641   if (!$AtomicNumber) {
1642     return 0;
1643   }
1644 
1645   $FormalCharge = $This->GetFormalCharge();
1646 
1647   # Is any valence model data available for atomic number and formal charge?
1648   if (!exists $ValenceModelDataRef->{$AtomicNumber}) {
1649     return 0;
1650   }
1651   if (!exists $ValenceModelDataRef->{$AtomicNumber}{$FormalCharge}) {
1652     return 0;
1653   }
1654 
1655   my($PotentialTotalValence, $SumOfBondOrders, $CurrentEffectiveValence, $AvailableCommonValence);
1656 
1657   $SumOfBondOrders = $This->GetSumOfBondOrders();
1658   if (!defined $SumOfBondOrders) {
1659     $SumOfBondOrders = 0;
1660   }
1661   $CurrentEffectiveValence = $SumOfBondOrders + $This->GetFreeRadicalElectrons();
1662 
1663   $PotentialTotalValence = 0;
1664   VALENCE: for $AvailableCommonValence (@{$ValenceModelDataRef->{$AtomicNumber}{$FormalCharge}{CommonValences}}) {
1665       if ($CurrentEffectiveValence <= $AvailableCommonValence) {
1666         $PotentialTotalValence = $AvailableCommonValence;
1667         last VALENCE;
1668       }
1669   }
1670 
1671   return $PotentialTotalValence;
1672 }
1673 
1674 #
1675 # For elements with one one common valence, potential total common valence used
1676 # during the calculation for number of implicit hydrogens during InternalValenceMode
1677 # corresponds to:
1678 #
1679 #   CommonValence + FormalCharge - FreeRadicalElectrons
1680 #
1681 # For elements with multiple common valences, each common valence is used to
1682 # calculate total potential common valence as shown above, and the first total potential
1683 # common valence gerater than the sum of bond orderes is selected as the final total
1684 # common valence.
1685 #
1686 # Group numbers > 14 - Group numbers 15 (N), 16 (O), 17 (F), 18 (He)
1687 #
1688 # Formal charge sign is not adjusted. Positive and negative values result in the
1689 # increase and decrease of valence.
1690 #
1691 # Group 14 containing C, Si, Ge, Sn, Pb...
1692 #
1693 # Formal charge sign is reversed for positive values. Both positive and negative
1694 # values result in the decrease of valence.
1695 #
1696 # Group 13 containing B, Al, Ga, In, Tl...
1697 #
1698 # Formal charge sign is always reversed. Positive and negative values result in the
1699 # decrease and increase of valence.
1700 #
1701 # Groups 1 (H) through 12 (Zn)...
1702 #
1703 # Formal charge sign is reversed for positive values. Both positive and negative
1704 # values result in the decrease of valence.
1705 #
1706 # Lanthanides and actinides...
1707 #
1708 # Formal charge sign is reversed for positive values. Both positive and negative
1709 # values result in the decrease of valence.
1710 #
1711 # Notes:
1712 #  . CommonValence and HighestCommonValence available from PeriodicTable module
1713 #    are equivalent to most common and highest sum of bond orders for an element. For
1714 #    neutral atoms involved only in single bonds, it corresponds to highest number of
1715 #    allowed bonds for the atom.
1716 #  . FormalCharge sign is reversed for electropositive elements with positive formal charge
1717 #    during common valence calculations. Electropositive elements, metals and transition elements,
1718 #    have usually plus formal charge and it leads to decrease in common valence; the negative
1719 #    formal charge should result in the decrease of common valence.
1720 #  . For carbon, both plus/minus formal charge cause decrease in common valence
1721 #  . For elements on the right of carbon in periodic table, electronegative elements, plus formal
1722 #    charge causes common valence to increase and minus formal charge cause it to decrease.
1723 #
1724 sub _GetPotentialTotalCommonValenceUsingInternalValenceModel {
1725   my($This) = @_;
1726   my($AtomicNumber, $CommonValences);
1727 
1728   $AtomicNumber = $This->{AtomicNumber};
1729   if (!$AtomicNumber) {
1730     return 0;
1731   }
1732 
1733   $CommonValences = PeriodicTable::GetElementCommonValences($AtomicNumber);
1734   if (!$CommonValences) {
1735     return 0;
1736   }
1737 
1738   my($PotentialTotalValence, $AdjustedFormalCharge, $FreeRadicalElectrons, $SumOfBondOrders, $AvailableCommonValence, @AvailableCommonValences);
1739 
1740   $AdjustedFormalCharge = $This->_GetFormalChargeAdjustedForInternalValenceModel();
1741   $FreeRadicalElectrons = $This->GetFreeRadicalElectrons();
1742 
1743   $SumOfBondOrders = $This->GetSumOfBondOrders();
1744   if (!defined $SumOfBondOrders) {
1745     $SumOfBondOrders = 0;
1746   }
1747 
1748   @AvailableCommonValences = split /\,/, $CommonValences;
1749 
1750   if (@AvailableCommonValences == 1) {
1751     # Calculate potential total valence using the only available common valence...
1752     $PotentialTotalValence = $AvailableCommonValences[0] + $AdjustedFormalCharge - $FreeRadicalElectrons;
1753   }
1754   else {
1755     # Calculate potential total valence using common valence from a list of available valences
1756     # that makes it higher than sum of bond orders or using the highest common valence...
1757     VALENCE: for $AvailableCommonValence (@AvailableCommonValences) {
1758       $PotentialTotalValence = $AvailableCommonValence + $AdjustedFormalCharge - $FreeRadicalElectrons;
1759 
1760       if ($PotentialTotalValence < 0 || $PotentialTotalValence >= $SumOfBondOrders) {
1761         last VALENCE;
1762       }
1763     }
1764   }
1765 
1766   return $PotentialTotalValence > 0 ? $PotentialTotalValence : 0;
1767 }
1768 
1769 # Adjust sign of the formal charge for potential total common valence calculation
1770 # used during internal valence model to figure out number of implicit hydrogens.
1771 #
1772 sub _GetFormalChargeAdjustedForInternalValenceModel {
1773   my($This) = @_;
1774   my($FormalCharge, $GroupNumber, $SwitchSign);
1775 
1776   $FormalCharge = $This->GetFormalCharge();
1777   if ($FormalCharge == 0) {
1778     return 0;
1779   }
1780 
1781   $GroupNumber = $This->GetGroupNumber();
1782   if (!defined $GroupNumber) {
1783     return $FormalCharge;
1784   }
1785 
1786   # Group numbers > 14 - Group numbers 15 (N), 16 (O), 17 (F), 18 (He)
1787   #
1788   # Formal charge sign is not adjusted. Positive and negative values result in the
1789   # increase and decrease of valence.
1790   #
1791   # Group 14 containing C, Si, Ge, Sn, Pb...
1792   #
1793   # Formal charge sign is reversed for positive values. Both positive and negative
1794   # values result in the decrease of valence.
1795   #
1796   # Group 13 containing B, Al, Ga, In, Tl...
1797   #
1798   # Formal charge sign is always reversed. Positive and negative values result in the
1799   # decrease and increase of valence.
1800   #
1801   # Groups 1 (H) through 12 (Zn)...
1802   #
1803   # Formal charge sign is reversed for positive values. Both positive and negative
1804   # values result in the decrease of valence.
1805   #
1806   # Lanthanides and actinides...
1807   #
1808   # Formal charge sign is reversed for positive values. Both positive and negative
1809   # values result in the decrease of valence.
1810   #
1811 
1812   $SwitchSign = 0;
1813   if (length $GroupNumber) {
1814     GROUPNUMBER: {
1815       if ($GroupNumber > 14) {
1816         # Groups on the right side of C group in the periodic table...
1817         $SwitchSign = 0;
1818         last GROUPNUMBER;
1819       }
1820       if ($GroupNumber == 14) {
1821         # Group containing C, Si, Ge, Sn, Pb...
1822         $SwitchSign = ($FormalCharge > 0) ? 1 : 0;
1823         last GROUPNUMBER;
1824       }
1825       if ($GroupNumber == 13) {
1826         # Group containing B, Al, Ga, In, Tl...
1827         $SwitchSign = 1;
1828         last GROUPNUMBER;
1829       }
1830       # Groups 1 (H) through 12 (Zn)...
1831       if ($GroupNumber >=1 && $GroupNumber <= 12) {
1832         # Groups 1 (H) through 12 (Zn)...
1833         $SwitchSign = ($FormalCharge > 0) ? 1 : 0;
1834         last GROUPNUMBER;
1835       }
1836     }
1837   }
1838   else {
1839     # Lanthanides and actinides...
1840     $SwitchSign = ($FormalCharge > 0) ? 1 : 0;
1841   }
1842 
1843   if ($SwitchSign) {
1844     $FormalCharge *= -1.0;
1845   }
1846 
1847   return $FormalCharge;
1848 }
1849 
1850 # Get lowest common valence...
1851 sub GetLowestCommonValence {
1852   my($This) = @_;
1853 
1854   # Is LowestCommonValence property explicitly set?
1855   if ($This->HasProperty('LowestCommonValence')) {
1856     return $This->GetProperty('LowestCommonValence');
1857   }
1858   my($AtomicNumber, $LowestCommonValence);
1859 
1860   $AtomicNumber = $This->{AtomicNumber};
1861   if (!$AtomicNumber) {
1862     return 0;
1863   }
1864   # Any need to differentiate between internal and other valence models...
1865 
1866   # LowestCommonValence is not set for all elements...
1867   $LowestCommonValence = PeriodicTable::GetElementLowestCommonValence($AtomicNumber);
1868   if (!$LowestCommonValence) {
1869     $LowestCommonValence = undef;
1870   }
1871 
1872   return $LowestCommonValence;
1873 }
1874 
1875 # Get highest common valence...
1876 sub GetHighestCommonValence {
1877   my($This) = @_;
1878 
1879   # Is HighestCommonValence property explicitly set?
1880   if ($This->HasProperty('HighestCommonValence')) {
1881     return $This->GetProperty('HighestCommonValence');
1882   }
1883   my($AtomicNumber, $HighestCommonValence);
1884 
1885   $AtomicNumber = $This->{AtomicNumber};
1886   if (!$AtomicNumber) {
1887     return 0;
1888   }
1889 
1890   # Any need to differentiate between internal and other valence models...
1891 
1892   # HighestCommonValence is not set for all elements...
1893   $HighestCommonValence = PeriodicTable::GetElementHighestCommonValence($AtomicNumber);
1894   if (!$HighestCommonValence) {
1895     $HighestCommonValence = undef;
1896   }
1897 
1898   return $HighestCommonValence;
1899 }
1900 
1901 # Get valence electrons...
1902 sub GetValenceElectrons {
1903   my($This) = @_;
1904 
1905   # Is ValenceElectrons property explicitly set?
1906   if ($This->HasProperty('ValenceElectrons')) {
1907     return $This->GetProperty('ValenceElectrons');
1908   }
1909   my($AtomicNumber, $ValenceElectrons);
1910 
1911   $AtomicNumber = $This->{AtomicNumber};
1912   if (!$AtomicNumber) {
1913     return 0;
1914   }
1915 
1916   $ValenceElectrons = PeriodicTable::GetElementValenceElectrons($AtomicNumber);
1917 
1918   return $ValenceElectrons;
1919 }
1920 
1921 # Add hydrogens to specified atom in molecule and return number of hydrogens added:
1922 #
1923 #   o HydrogensToAdd = ImplicitHydrogenCount - ExplicitHydrogenCount
1924 #
1925 #   o XYZ are set to ZeroVector
1926 #
1927 sub AddHydrogens {
1928   my($This, $HydrogenPositionsWarning) = @_;
1929 
1930   # Is this atom in a molecule?
1931   if (!$This->HasProperty('Molecule')) {
1932     return undef;
1933   }
1934   if (!defined $HydrogenPositionsWarning) {
1935     $HydrogenPositionsWarning = 1;
1936   }
1937   if ($HydrogenPositionsWarning) {
1938     carp "Warning: ${ClassName}->AddHydrogens: The current release of MayaChemTools doesn't assign any hydrogen positions...";
1939   }
1940 
1941   # Is it an element symbol?
1942   if (!$This->{AtomicNumber}) {
1943     return 0;
1944   }
1945 
1946   my($Molecule, $HydrogensAdded, $HydrogensToAdd);
1947 
1948   $Molecule = $This->GetProperty('Molecule');
1949   $HydrogensAdded = 0;
1950   $HydrogensToAdd = $This->GetNumOfMissingHydrogens();
1951   if ($HydrogensToAdd <= 0) {
1952     return $HydrogensAdded;
1953   }
1954 
1955   my($Count, $Hydrogen);
1956 
1957   for $Count (1 .. $HydrogensToAdd) {
1958     $HydrogensAdded++;
1959 
1960     $Hydrogen = $Molecule->NewAtom('AtomSymbol' => 'H', 'XYZ' => [0, 0, 0]);
1961     $Molecule->NewBond('Atoms' => [$This, $Hydrogen], 'BondOrder' => 1);
1962   }
1963 
1964   return $HydrogensAdded;
1965 }
1966 
1967 # Delete hydrogens attached to atom in molecule and return total number of hydrogens deleted...
1968 sub DeleteHydrogens {
1969   my($This) = @_;
1970 
1971   # Is this atom in a molecule?
1972   if (!$This->HasProperty('Molecule')) {
1973     return undef;
1974   }
1975 
1976   # Is it an element symbol?
1977   if (!$This->{AtomicNumber}) {
1978     return 0;
1979   }
1980 
1981   my($Molecule, $Neighbor, $HydrogensDeleted, @Neighbors);
1982 
1983   $Molecule = $This->GetProperty('Molecule');
1984   $HydrogensDeleted = 0;
1985   @Neighbors = $This->GetNeighbors();
1986 
1987   NEIGHBOR: for $Neighbor (@Neighbors) {
1988     if (!$Neighbor->IsHydrogen()) {
1989       next NEIGHBOR;
1990     }
1991     $Molecule->_DeleteAtom($Neighbor);
1992     $HydrogensDeleted++;
1993   }
1994 
1995   return $HydrogensDeleted;
1996 }
1997 
1998 # Copy atom and all its associated data...
1999 sub Copy {
2000   my($This) = @_;
2001   my($Atom);
2002 
2003   $Atom = Storable::dclone($This);
2004 
2005   return $Atom;
2006 }
2007 
2008 # Get atomic invariant value...
2009 #
2010 sub GetAtomicInvariantValue {
2011   my($This, $AtomicInvariant) = @_;
2012   my($Value);
2013 
2014   $Value = "";
2015 
2016   ATOMICVARIANT: {
2017     if ($AtomicInvariant =~ /^(AS|AtomSymbol|ElementSymbol)$/i) {
2018       $Value = $This->GetAtomSymbol();
2019       last ATOMICVARIANT;
2020     }
2021     if ($AtomicInvariant =~ /^(X|NumOfNonHydrogenAtomNeighbors|NumOfHeavyAtomNeighbors)$/i) {
2022       $Value = $This->GetNumOfNonHydrogenAtomNeighbors();
2023       last ATOMICVARIANT;
2024     }
2025     if ($AtomicInvariant =~ /^(BO|SumOfBondOrdersToNonHydrogenAtoms|SumOfBondOrdersToHeavyAtoms)$/i) {
2026       $Value = $This->GetSumOfBondOrdersToNonHydrogenAtoms();
2027       last ATOMICVARIANT;
2028     }
2029     if ($AtomicInvariant =~ /^(LBO|LargestBondOrderToNonHydrogenAtoms|LargestBondOrderToHeavyAtoms)$/i) {
2030       $Value = $This->GetLargestBondOrderToNonHydrogenAtoms();
2031       last ATOMICVARIANT;
2032     }
2033     if ($AtomicInvariant =~ /^(H|NumOfImplicitAndExplicitHydrogens)$/i) {
2034       $Value = $This->GetNumOfHydrogens();
2035       last ATOMICVARIANT;
2036     }
2037     if ($AtomicInvariant =~ /^(SB|NumOfSingleBondsToNonHydrogenAtoms|NumOfSingleBondsToHeavyAtoms)$/i) {
2038       $Value = $This->GetNumOfSingleBondsToNonHydrogenAtoms();
2039       last ATOMICVARIANT;
2040     }
2041     if ($AtomicInvariant =~ /^(DB|NumOfDoubleBondsToNonHydrogenAtoms|NumOfDoubleBondsToHeavyAtoms)$/i) {
2042       $Value = $This->GetNumOfDoubleBondsToNonHydrogenAtoms();
2043       last ATOMICVARIANT;
2044     }
2045     if ($AtomicInvariant =~ /^(TB|NumOfTripleBondsToNonHydrogenAtoms|NumOfTripleBondsToHeavyAtoms)$/i) {
2046       $Value = $This->GetNumOfTripleBondsToNonHydrogenAtoms();
2047       last ATOMICVARIANT;
2048     }
2049     if ($AtomicInvariant =~ /^(AB|NumOfAromaticBondsToNonHydrogenAtoms|NumOfAromaticBondsToHeavyAtoms)$/i) {
2050       $Value = $This->GetNumOfAromaticBondsToNonHydrogenAtoms();
2051       last ATOMICVARIANT;
2052     }
2053     if ($AtomicInvariant =~ /^(FC|FormalCharge)$/i) {
2054       $Value = $This->GetFormalCharge();
2055       $Value = defined $Value ? $Value : 0;
2056       last ATOMICVARIANT;
2057     }
2058     if ($AtomicInvariant =~ /^(T|TotalNumOfAtomNeighbors)$/i) {
2059       $Value = $This->GetNumOfNonHydrogenAtomNeighbors() + $This->GetNumOfHydrogens();
2060       last ATOMICVARIANT;
2061     }
2062     if ($AtomicInvariant =~ /^(TSB|TotalNumOfSingleBonds)$/i) {
2063       $Value = $This->GetNumOfSingleBondsToNonHydrogenAtoms() + $This->GetNumOfHydrogens();
2064       last ATOMICVARIANT;
2065     }
2066     if ($AtomicInvariant =~ /^(Ar|Aromatic)$/i) {
2067       $Value = $This->IsAromatic() ? 1 : 0;
2068       last ATOMICVARIANT;
2069     }
2070     if ($AtomicInvariant =~ /^(RA|RingAtom)$/i) {
2071       $Value = $This->IsInRing() ? 1 : 0;
2072       last ATOMICVARIANT;
2073     }
2074     if ($AtomicInvariant =~ /^(Str|Stereochemistry)$/i) {
2075       $Value = $This->GetStereochemistry();
2076       $Value= (defined($Value) && ($Value =~ /^(R|S)$/i)) ? $Value : '';
2077       last ATOMICVARIANT;
2078     }
2079     if ($AtomicInvariant =~ /^(AN|AtomicNumber)$/i) {
2080       $Value = $This->GetAtomicNumber();
2081       last ATOMICVARIANT;
2082     }
2083     if ($AtomicInvariant =~ /^(AM|AtomicMass)$/i) {
2084       $Value = round($This->GetExactMass(), 4) + 0;
2085       last ATOMICVARIANT;
2086     }
2087     if ($AtomicInvariant =~ /^(MN|MassNumber)$/i) {
2088       $Value = $This->GetMassNumber();
2089       last ATOMICVARIANT;
2090     }
2091     if ($AtomicInvariant =~ /^(SM|SpinMultiplicity)$/i) {
2092       $Value = $This->GetSpinMultiplicity();
2093       $Value = defined $Value ? $Value : '';
2094       last ATOMICVARIANT;
2095     }
2096     $Value = "";
2097     carp "Warning: ${ClassName}->GetAtomicInvariantValue: Unknown atomic invariant $AtomicInvariant...";
2098   }
2099 
2100   return $Value;
2101 }
2102 
2103 # Get period number of the atom..
2104 #
2105 sub GetPeriodNumber {
2106   my($This) = @_;
2107 
2108   # Is PeriodNumber property explicitly set?
2109   if ($This->HasProperty('PeriodNumber')) {
2110     return $This->GetProperty('PeriodNumber');
2111   }
2112   my($AtomicNumber, $PeriodNumber);
2113 
2114   $AtomicNumber = $This->{AtomicNumber};
2115   if (!$AtomicNumber) {
2116     return 0;
2117   }
2118 
2119   $PeriodNumber = PeriodicTable::GetElementPeriodNumber($AtomicNumber);
2120 
2121   return $PeriodNumber;
2122 }
2123 
2124 # Get group number of the atom..
2125 #
2126 sub GetGroupNumber {
2127   my($This) = @_;
2128 
2129   # Is GroupNumber property explicitly set?
2130   if ($This->HasProperty('GroupNumber')) {
2131     return $This->GetProperty('GroupNumber');
2132   }
2133   my($AtomicNumber, $GroupNumber);
2134 
2135   $AtomicNumber = $This->{AtomicNumber};
2136   if (!$AtomicNumber) {
2137     return 0;
2138   }
2139 
2140   $GroupNumber = PeriodicTable::GetElementGroupNumber($AtomicNumber);
2141 
2142   return $GroupNumber;
2143 }
2144 
2145 # Is it a specified topological pharmacophore atom type?
2146 #
2147 sub IsTopologicalPharmacophoreType {
2148   my($This, $Type) = @_;
2149 
2150   return $This->_IsFunctionalClassType($Type);
2151 }
2152 
2153 # Is it a specified functional class atom type?
2154 #
2155 sub IsFunctionalClassType {
2156   my($This, $Type) = @_;
2157 
2158   return $This->_IsFunctionalClassType($Type);
2159 }
2160 
2161 # Is it a specified functional/topological pharmacophore atom type?
2162 #
2163 sub _IsFunctionalClassType {
2164   my($This, $Type) = @_;
2165   my($Value);
2166 
2167   $Value = 0;
2168 
2169   TYPE: {
2170     if ($Type =~ /^(HBD|HydrogenBondDonor)$/i) {
2171       $Value = $This->IsHydrogenBondDonor();
2172       last TYPE;
2173     }
2174     if ($Type =~ /^(HBA|HydrogenBondAcceptor)$/i) {
2175       $Value = $This->IsHydrogenBondAcceptor();
2176       last TYPE;
2177     }
2178     if ($Type =~ /^(PI|PositivelyIonizable)$/i) {
2179       $Value = $This->IsPositivelyIonizable();
2180       last TYPE;
2181     }
2182     if ($Type =~ /^(NI|NegativelyIonizable)$/i) {
2183       $Value = $This->IsNegativelyIonizable();
2184       last TYPE;
2185     }
2186     if ($Type =~ /^(H|Hydrophobic)$/i) {
2187       $Value = $This->IsHydrophobic();
2188       last TYPE;
2189     }
2190     if ($Type =~ /^(Ar|Aromatic)$/i) {
2191       $Value = $This->IsAromatic();
2192       last TYPE;
2193     }
2194     if ($Type =~ /^(Hal|Halogen)$/i) {
2195       $Value = $This->IsHalogen();
2196       last TYPE;
2197     }
2198     if ($Type =~ /^(RA|RingAtom)$/i) {
2199       $Value = $This->IsInRing();
2200       last TYPE;
2201     }
2202     if ($Type =~ /^(CA|ChainAtom)$/i) {
2203       $Value = $This->IsNotInRing();
2204       last TYPE;
2205     }
2206     $Value = 0;
2207     carp "Warning: ${ClassName}->_IsType: Unknown functional/pharmacohore type $Type...";
2208   }
2209   return $Value;
2210 }
2211 
2212 # Is it a Hydrogen atom?
2213 sub IsHydrogen {
2214   my($This) = @_;
2215 
2216   return ($This->{AtomicNumber} == 1) ? 1 : 0;
2217 }
2218 
2219 # Is it a Carbon atom?
2220 sub IsCarbon {
2221   my($This) = @_;
2222 
2223   return ($This->{AtomicNumber} == 6) ? 1 : 0;
2224 }
2225 
2226 # Is it a Nitrogen atom?
2227 sub IsNitrogen {
2228   my($This) = @_;
2229 
2230   return ($This->{AtomicNumber} == 7) ? 1 : 0;
2231 }
2232 
2233 # Is it a Oxygen atom?
2234 sub IsOxygen {
2235   my($This) = @_;
2236 
2237   return ($This->{AtomicNumber} == 8) ? 1 : 0;
2238 }
2239 
2240 # Is it a Fluorine atom?
2241 sub IsFluorine {
2242   my($This) = @_;
2243 
2244   return ($This->{AtomicNumber} == 9) ? 1 : 0;
2245 }
2246 
2247 # Is it a Silicon atom?
2248 sub IsSilicon {
2249   my($This) = @_;
2250 
2251   return ($This->{AtomicNumber} == 14) ? 1 : 0;
2252 }
2253 
2254 # Is it a Phosphorus atom?
2255 sub IsPhosphorus {
2256   my($This) = @_;
2257 
2258   return ($This->{AtomicNumber} == 15) ? 1 : 0;
2259 }
2260 
2261 # Is it a Sulphur atom?
2262 sub IsSulphur {
2263   my($This) = @_;
2264 
2265   return $This->IsSulfur();
2266 }
2267 
2268 # Is it a Sulfur atom?
2269 sub IsSulfur {
2270   my($This) = @_;
2271 
2272   return ($This->{AtomicNumber} == 16) ? 1 : 0;
2273 }
2274 
2275 # Is it a Chlorine atom?
2276 sub IsChlorine {
2277   my($This) = @_;
2278 
2279   return ($This->{AtomicNumber} == 17) ? 1 : 0;
2280 }
2281 
2282 # Is it a Arsenic atom?
2283 sub IsArsenic {
2284   my($This) = @_;
2285 
2286   return ($This->{AtomicNumber} == 33) ? 1 : 0;
2287 }
2288 
2289 # Is it a Selenium atom?
2290 sub IsSelenium {
2291   my($This) = @_;
2292 
2293   return ($This->{AtomicNumber} == 34) ? 1 : 0;
2294 }
2295 
2296 # Is it a Bromine atom?
2297 sub IsBromine {
2298   my($This) = @_;
2299 
2300   return ($This->{AtomicNumber} == 35) ? 1 : 0;
2301 }
2302 
2303 # Is it a Tellurium atom?
2304 sub IsTellurium {
2305   my($This) = @_;
2306 
2307   return ($This->{AtomicNumber} == 52) ? 1 : 0;
2308 }
2309 
2310 # Is it a Iodine atom?
2311 sub IsIodine {
2312   my($This) = @_;
2313 
2314   return ($This->{AtomicNumber} == 53) ? 1 : 0;
2315 }
2316 
2317 # Is it a hetro atom? (N, O, F, P, S, Cl, Br, I)
2318 sub IsHeteroAtom {
2319   my($This) = @_;
2320 
2321   return ($This->{AtomicNumber} =~ /^(7|8|9|15|16|17|35|53)$/) ? 1 : 0;
2322 }
2323 
2324 # Is it a halogen atom? (F, Cl, Br, I)
2325 sub IsHalogen {
2326   my($This) = @_;
2327 
2328   return ($This->{AtomicNumber} =~ /^(9|17|35|53)$/) ? 1 : 0;
2329 }
2330 
2331 # Is it classified as metallic?
2332 sub IsMetallic {
2333   my($This) = @_;
2334   my($Classification);
2335 
2336   $Classification = PeriodicTable::GetElementClassification($This->{AtomicNumber});
2337 
2338   return ($Classification =~ /^Metallic$/i) ? 1 : 0;
2339 }
2340 
2341 # Is it a non carbon or hydrogen atom? (C, H)
2342 sub IsNonCarbonOrHydrogen {
2343   my($This) = @_;
2344 
2345   return ($This->{AtomicNumber} =~ /^(1|6)$/) ? 0 : 1;
2346 }
2347 
2348 # Is it a polar atom? ( N, O,  P, S)
2349 sub IsPolarAtom {
2350   my($This) = @_;
2351 
2352   return ($This->{AtomicNumber} =~ /^(7|8|15|16)$/) ? 1 : 0;
2353 }
2354 
2355 # Is it an isotope?
2356 sub IsIsotope {
2357   my($This) = @_;
2358 
2359   my($AtomicNumber) = $This->{AtomicNumber};
2360   if (!$AtomicNumber) {
2361     return 0;
2362   }
2363 
2364   if (!$This->HasProperty('MassNumber')) {
2365     return 0;
2366   }
2367   my($MassNumber, $MostAbundantMassNumber);
2368 
2369   $MassNumber = $This->GetProperty('MassNumber');
2370   $MostAbundantMassNumber = PeriodicTable::GetElementMostAbundantNaturalIsotopeMassNumber($AtomicNumber);
2371 
2372   return ($MassNumber == $MostAbundantMassNumber) ? 0 : 1;
2373 }
2374 
2375 # Is it a terminal atom?
2376 sub IsTerminal {
2377   my($This) = @_;
2378 
2379   # Is this atom in a molecule?
2380   if (!$This->HasProperty('Molecule')) {
2381     return undef;
2382   }
2383 
2384   return ($This->GetNumOfNonHydrogenAtomNeighbors() <= 1) ? 1 : 0
2385 
2386 }
2387 
2388 # Is aromatic property set for the atom?
2389 sub IsAromatic {
2390   my($This) = @_;
2391   my($Aromatic);
2392 
2393   $Aromatic = $This->GetAromatic();
2394 
2395   return (defined($Aromatic) && $Aromatic) ? 1 : 0;
2396 }
2397 
2398 # Is this a hydrogen atom and attached to one of these atoms: N, O, P, S
2399 sub IsPolarHydrogen {
2400   my($This) = @_;
2401 
2402   if (!$This->IsHydrogen()) {
2403     return 0;
2404   }
2405 
2406   my(@Bonds);
2407   @Bonds = $This->GetBonds();
2408   if (@Bonds > 1) {
2409     return 0;
2410   }
2411 
2412   my($Bond, $BondedAtom);
2413   ($Bond) = @Bonds;
2414   $BondedAtom = $Bond->GetBondedAtom($This);
2415 
2416   return $BondedAtom->IsPolarAtom() ? 1 : 0;
2417 }
2418 
2419 # Is it a hydrogen bond donor atom?
2420 #
2421 sub IsHBondDonor {
2422   my($This, $HydrogenBondsType) = @_;
2423 
2424   return $This->IsHydrogenBondDonor($HydrogenBondsType);
2425 }
2426 
2427 # The currrent release of MayaChemTools supports identification of two types of
2428 # hydrogen bond donor and acceptor atoms with these names:
2429 #
2430 # HBondsType1 or HydrogenBondsType1
2431 # HBondsType2 or HydrogenBondsType2
2432 #
2433 # The names of these hydrogen bond types are rather arbitrary. However, their
2434 # definitions have specific meaning and are as follows:
2435 #
2436 # HydrogenBondsType1 [ Ref 60-61, Ref 65-66 ]:
2437 #   . Donor: NH, NH2, NH3, OH - Any N and O with available H
2438 #   . Acceptor: N[!H], O - Any N without available H and any O
2439 #
2440 # HydrogenBondsType2 [ Ref 91 ]:
2441 #   . Donor: NH, NH2, NH3, OH - Any N and O with availabe H
2442 #   . Acceptor: N, O - Any N and O
2443 #
2444 # Note:
2445 #   . HydrogenBondsType2 definition corresponds to Rule of 5.
2446 #
2447 
2448 # Is it a hydrogen bond donor atom?
2449 #
2450 # The currrent release of MayaChemTools supports identification of two types of
2451 sub IsHydrogenBondDonor {
2452   my($This, $HydrogenBondsType) = @_;
2453   my($Status);
2454 
2455   $HydrogenBondsType = defined $HydrogenBondsType ? $HydrogenBondsType : 'HBondsType1';
2456   $Status = 0;
2457 
2458   HYDROGENBONDSTYPE: {
2459 
2460       if ($HydrogenBondsType =~ /^(HBondsType1|HydrogenBondsType1)$/i) {
2461         $Status = $This->_IsHydrogenBondDonorOfType1();
2462         last HYDROGENBONDSTYPE;
2463       }
2464 
2465       if ($HydrogenBondsType =~ /^(HBondsType2|HydrogenBondsType2)$/i) {
2466         $Status = $This->_IsHydrogenBondDonorOfType2();
2467         last HYDROGENBONDSTYPE;
2468       }
2469 
2470       $Status = 0;
2471       carp "Warning: ${ClassName}->IsHydrogenBondDonor: The current release of MayaChemTools doesn't support specified value, $HydrogenBondsType, for HydrogenBondsType. Valid values: HBondsType1, HydrogenBondsType1, HBondsType2 HydrogenBondsType2 ...";
2472   }
2473 
2474   return $Status;
2475 }
2476 
2477 # Is it a MayaChemTools HBondType1 hydrogen bond donor atom?
2478 #
2479 sub _IsHydrogenBondDonorOfType1 {
2480   my($This) = @_;
2481 
2482   return $This->_IsHydrogenBondDonorOfType1OrType2();
2483 }
2484 
2485 # Is it a MayaChemTools HBondType2 hydrogen bond donor atom?
2486 #
2487 sub _IsHydrogenBondDonorOfType2 {
2488   my($This) = @_;
2489 
2490   return $This->_IsHydrogenBondDonorOfType1OrType2();
2491 }
2492 
2493 # Is it a hydrogen bond donor atom of MayaChemTools Type1 or Type2?
2494 #
2495 # HydrogenBondDonor definition [ Ref 60-61, Ref 65-66, Ref 91 ]: NH, NH2, OH
2496 #
2497 # In other words:
2498 #   . NH, NH2 - Nitrogen atom with available hydrogen
2499 #   . OH - Oxygen atom with avilable hydrogen
2500 #
2501 sub _IsHydrogenBondDonorOfType1OrType2 {
2502   my($This) = @_;
2503 
2504   # Is this atom in a molecule?
2505   if (!$This->HasProperty('Molecule')) {
2506     return 0;
2507   }
2508 
2509   # Is it N or O?
2510   if ($This->{AtomicNumber} !~ /^(7|8)$/) {
2511     return 0;
2512   }
2513 
2514   # Any explicitly attached hydrogens?
2515   if ($This->GetExplicitHydrogens()) {
2516     return 1;
2517   }
2518 
2519   # Any missing hydrogens?
2520   return $This->GetNumOfMissingHydrogens() ? 1 : 0;
2521 }
2522 
2523 # Is it a hydrogen bond acceptor atom?
2524 #
2525 sub IsHBondAcceptor {
2526   my($This, $HydrogenBondsType) = @_;
2527 
2528   return $This->IsHydrogenBondAcceptor($HydrogenBondsType);
2529 }
2530 
2531 # Is it a hydrogen bond acceptor atom?
2532 #
2533 sub IsHydrogenBondAcceptor {
2534   my($This, $HydrogenBondsType) = @_;
2535   my($Status);
2536 
2537   $HydrogenBondsType = defined $HydrogenBondsType ? $HydrogenBondsType : 'HBondsType1';
2538   $Status = 0;
2539 
2540   HYDROGENBONDSTYPE: {
2541 
2542       if ($HydrogenBondsType =~ /^(HBondsType1|HydrogenBondsType1)$/i) {
2543         $Status = $This->_IsHydrogenBondAcceptorOfType1();
2544         last HYDROGENBONDSTYPE;
2545       }
2546 
2547       if ($HydrogenBondsType =~ /^(HBondsType2|HydrogenBondsType2)$/i) {
2548         $Status = $This->_IsHydrogenBondAcceptorOfType2();
2549         last HYDROGENBONDSTYPE;
2550       }
2551 
2552       $Status = 0;
2553       carp "Warning: ${ClassName}->IsHydrogenBondAcceptor: The current release of MayaChemTools doesn't support specified value, $HydrogenBondsType, for HydrogenBondsType. Valid values: HBondsType1, HydrogenBondsType1, HBondsType2 HydrogenBondsType2 ...";
2554   }
2555 
2556   return $Status;
2557 }
2558 
2559 # Is it a MayaChemTools HBondType1 hydrogen bond acceptor atom?
2560 #
2561 # HydrogenBondAcceptor definition [ Ref 60-61, Ref 65-66 ]: N[!H], O
2562 #
2563 # In other words:
2564 #   . N[!H] - Nitrogen atom with no hydrogen
2565 #   . O - Oxygen atom
2566 #
2567 sub _IsHydrogenBondAcceptorOfType1 {
2568   my($This) = @_;
2569 
2570   # Is this atom in a molecule?
2571   if (!$This->HasProperty('Molecule')) {
2572     return 0;
2573   }
2574 
2575   # Is it N or O?
2576   if ($This->{AtomicNumber} !~ /^(7|8)$/) {
2577     return 0;
2578   }
2579 
2580   # Is it O?
2581   if ($This->{AtomicNumber} == 8 ) {
2582     return 1;
2583   }
2584 
2585   # Any explicitly attached hydrogens?
2586   if ($This->GetExplicitHydrogens()) {
2587     return 0;
2588   }
2589 
2590   # Any missing hydrogens?
2591   return $This->GetNumOfMissingHydrogens() ? 0 : 1;
2592 }
2593 
2594 # Is it a MayaChemTools HBondType2 hydrogen bond acceptor atom?
2595 #
2596 # HydrogenBondAcceptor definition [ Ref 91 ]: N, O
2597 #
2598 # In other words:
2599 #   . Any Nitrogen or Oxygen atom
2600 #
2601 # Note:
2602 #   . HydrogenBondsType2 definition corresponds to Rule of 5.
2603 #
2604 sub _IsHydrogenBondAcceptorOfType2 {
2605   my($This) = @_;
2606 
2607   # Is this atom in a molecule?
2608   if (!$This->HasProperty('Molecule')) {
2609     return 0;
2610   }
2611 
2612   return ($This->{AtomicNumber} =~ /^(7|8)$/) ? 1 : 0;
2613 }
2614 
2615 # Is it a positively ionizable atom?
2616 #
2617 # PositivelyIonizable defintion [ Ref 60-61, Ref 65-66 ]: +, NH2
2618 #
2619 # In other words:
2620 #   . Any atom with positve formal charge
2621 #   . NH2 - Nitogen atom in amino group
2622 #
2623 sub IsPositivelyIonizable {
2624   my($This) = @_;
2625   my($FormalCharge);
2626 
2627   # Is this atom in a molecule?
2628   if (!$This->HasProperty('Molecule')) {
2629     return 0;
2630   }
2631 
2632   # Any explicit positive formal charge?
2633   $FormalCharge = $This->GetFormalCharge();
2634   if (defined($FormalCharge) && $FormalCharge > 0) {
2635     return 1;
2636   }
2637 
2638   # Is it  N?
2639   if ($This->{AtomicNumber} != 7 ) {
2640     return 0;
2641   }
2642 
2643   return ($This->GetNumOfHydrogens() == 2) ? 1 : 0;
2644 }
2645 
2646 # Is it a negatively ionizable atom?
2647 #
2648 # NegativelyIonizable definition [ Ref 60-61, Ref 65-66 ]: -, C(=O)OH, S(=O)OH, P(=O)OH
2649 #
2650 # In other words:
2651 #   . Any atom with negative formal charge
2652 #   . Carbon atom in C(=O)OH group
2653 #   . Phosphorous in P(=O)OH group
2654 #   . Sulfur atom in S(=O)OH group
2655 #
2656 sub IsNegativelyIonizable {
2657   my($This) = @_;
2658   my($FormalCharge);
2659 
2660   # Is this atom in a molecule?
2661   if (!$This->HasProperty('Molecule')) {
2662     return 0;
2663   }
2664 
2665   # Any explicit negative formal charge?
2666   $FormalCharge = $This->GetFormalCharge();
2667   if (defined($FormalCharge) && $FormalCharge < 0) {
2668     return 1;
2669   }
2670 
2671   # Is it C, P or S?
2672   if ($This->{AtomicNumber} !~ /^(6|15|16)$/ ) {
2673     return 0;
2674   }
2675 
2676   # Collect oxygens connected to C, P or S with single or double bonds and not connected to
2677   # any other heavy atom...
2678   my($Neighbor, $NeighborOxygenBondOrder, $NumOfNeighborOxygensWithSingleBonds, $NumOfNeighborOxygensWithDoubleBonds);
2679 
2680   $NumOfNeighborOxygensWithSingleBonds = 0; $NumOfNeighborOxygensWithDoubleBonds = 0;
2681 
2682   NEIGHBOR: for $Neighbor ($This->GetNeighbors()) {
2683     # Is it an oxygen?
2684     if ($Neighbor->{AtomicNumber} != 8) {
2685       next NEIGHBOR;
2686     }
2687     # Is oxygent connected to only heavy atom?
2688     if ($Neighbor->GetNumOfHeavyAtomNeighbors() != 1) {
2689       next NEIGHBOR;
2690     }
2691     $NeighborOxygenBondOrder = $This->GetBondToAtom($Neighbor)->GetBondOrder();
2692 
2693     if ($NeighborOxygenBondOrder == 2) {
2694       $NumOfNeighborOxygensWithDoubleBonds++;
2695     }
2696     elsif ($NeighborOxygenBondOrder == 1) {
2697       $NumOfNeighborOxygensWithSingleBonds++;
2698     }
2699   }
2700   return ($NumOfNeighborOxygensWithDoubleBonds >= 1 && $NumOfNeighborOxygensWithSingleBonds >= 1) ? 1 : 0;
2701 }
2702 
2703 # Is it a liphophilic atom?
2704 #
2705 # Lipophilic definition [ Ref 60-61, Ref 65-66 ]: C(C)(C)(C)(C), Cl, Br, I, S(C)(C)
2706 #
2707 # In other words:
2708 #   . C(C)(C)(C)(C) - Carbon atom connected to only other carbons
2709 #   . Chlorine, Bromine or Iodine atom
2710 #   . S(C)(C) - Sulfur connected to two carbons
2711 #
2712 sub IsLipophilic {
2713   my($This) = @_;
2714 
2715   # Is this atom in a molecule?
2716   if (!$This->HasProperty('Molecule')) {
2717     return 0;
2718   }
2719 
2720   # Is it Cl, Br, I?
2721   if ($This->{AtomicNumber} =~ /^(17|35|53)$/) {
2722     return 1;
2723   }
2724 
2725   # Is it C, S?
2726   if ($This->{AtomicNumber} !~ /^(6|16)$/) {
2727     return 0;
2728   }
2729 
2730   # Are all heavy atom neighbors Carbons?
2731   my($HeavyAtomNeighbor, @HeavyAtomNeighbors);
2732   @HeavyAtomNeighbors = ();
2733   @HeavyAtomNeighbors = $This->GetHeavyAtomNeighbors();
2734 
2735   for $HeavyAtomNeighbor (@HeavyAtomNeighbors) {
2736     if ($HeavyAtomNeighbor->{AtomicNumber} != 6) {
2737       return 0;
2738     }
2739   }
2740 
2741   # Does sulfur has two carbon neighbors?
2742   if ($This->{AtomicNumber} == 16) {
2743     if (@HeavyAtomNeighbors != 2) {
2744       return 0;
2745     }
2746   }
2747   return 1;
2748 }
2749 
2750 # Is it hydrophobic?
2751 #
2752 sub IsHydrophobic {
2753   my($This) = @_;
2754 
2755   return $This->IsLipophilic();
2756 }
2757 
2758 # Is it a Nitrogen atom in Guadinium group?
2759 #
2760 sub IsGuadiniumNitrogen {
2761   my($This) = @_;
2762 
2763   # Is it Nitrogen?
2764   if (!$This->IsNitrogen()) {
2765     return 0;
2766   }
2767 
2768   # Is it connected to a Guadinium Carbon?
2769   my($AtomNeighbor);
2770 
2771   for $AtomNeighbor ($This->GetNonHydrogenAtomNeighbors()) {
2772     if ($AtomNeighbor->IsGuadiniumCarbon()) {
2773       return 1;
2774     }
2775   }
2776 
2777   return 0;
2778 }
2779 
2780 # Is it a Carbon atom in Guadinium group?
2781 #
2782 # Guadinium group definition:
2783 #
2784 #   R2N-C(=NR)-(NR2) or R2N-C(=NR2+)-(NR2)
2785 #
2786 #   where:
2787 #      . R = Hydrogens or group of atoms attached through Carbon
2788 #      . Only one of the three Nitrogens has a double bond to Carbon and has optional
2789 #        formal charge allowing it to be neutral or charged state
2790 #
2791 sub IsGuadiniumCarbon {
2792   my($This) = @_;
2793 
2794   # Is it Carbon?
2795   if (!$This->IsCarbon()) {
2796     return 0;
2797   }
2798 
2799   # Match atom neighborhood...
2800   my($CentralAtomSpec, @NbrAtomSpecsRef, @NbrBondSpecsRef, @NbrOfNbrAtomSpecsRef);
2801 
2802   $CentralAtomSpec = 'C.X3.BO4';
2803   @NbrAtomSpecsRef = ('N.FC0', 'N.FC0', 'N.FC0,N.FC+1');
2804   @NbrBondSpecsRef = ('-', '-', '=');
2805   @NbrOfNbrAtomSpecsRef = ('C,H', 'C,H', 'C,H');
2806 
2807   if ($This->DoesAtomNeighborhoodMatch($CentralAtomSpec, \@NbrAtomSpecsRef, \@NbrBondSpecsRef, \@NbrOfNbrAtomSpecsRef)) {
2808     return 1;
2809   }
2810 
2811   return 0;
2812 }
2813 
2814 # Is it a Nitrogen atom in Amide group?
2815 #
2816 sub IsAmideNitrogen {
2817   my($This) = @_;
2818 
2819   # Is it Nitrogen?
2820   if (!$This->IsNitrogen()) {
2821     return 0;
2822   }
2823 
2824   # Is it connected to a Amide Carbon?
2825   my($AtomNeighbor);
2826 
2827   for $AtomNeighbor ($This->GetNonHydrogenAtomNeighbors()) {
2828     if ($AtomNeighbor->IsAmideCarbon()) {
2829       return 1;
2830     }
2831   }
2832 
2833   return 0;
2834 }
2835 
2836 # Is it a Carbon atom in Amide group?
2837 #
2838 # Amide group definition: R-C(=O)-N(-R')-R''
2839 #
2840 #   where:
2841 #      . R = Hydrogen or groups of atoms attached through Carbon
2842 #      . R' = Hydrogens or groups of atoms attached through Carbon or hetro atoms
2843 #      . R'' = Hydrogens or groups of atoms attached through Carbon or hetro atoms
2844 #
2845 sub IsAmideCarbon {
2846   my($This) = @_;
2847 
2848   # Is this atom in a molecule?
2849   if (!$This->HasProperty('Molecule')) {
2850     return 0;
2851   }
2852 
2853   # Is it Carbon?
2854   if (!$This->IsCarbon()) {
2855     return 0;
2856   }
2857 
2858   # Match atom neighborhood...
2859   my($CentralAtomSpec, @NbrAtomSpecsRef, @NbrBondSpecsRef, @NbrOfNbrAtomSpecsRef);
2860 
2861   $CentralAtomSpec = 'C.X3.BO4,C.X2.BO3';
2862   @NbrAtomSpecsRef = ('C,H', 'O', 'N');
2863   @NbrBondSpecsRef = ('-', '=', '-');
2864   @NbrOfNbrAtomSpecsRef = ('C,H', 'C', 'C,H,N,O,S,P,F,Cl,Br,I');
2865 
2866   if ($This->DoesAtomNeighborhoodMatch($CentralAtomSpec, \@NbrAtomSpecsRef, \@NbrBondSpecsRef, \@NbrOfNbrAtomSpecsRef)) {
2867     return 1;
2868   }
2869 
2870   return 0;
2871 }
2872 
2873 # Is it a Oxygen atom in Carboxylate group?
2874 #
2875 sub IsCarboxylateOxygen {
2876   my($This) = @_;
2877 
2878   return $This->_MatchCarboxylateAndOrCarboxylOxygen('Carboxylate');
2879 }
2880 
2881 # Is it a Carbon atom in Carboxylate group?
2882 #
2883 # Carboxyl group definition: R-C(=O)-O-
2884 #
2885 sub IsCarboxylateCarbon {
2886   my($This) = @_;
2887 
2888   return $This->_MatchCarboxylateAndOrCarboxylCarbon('Carboxylate');
2889 }
2890 
2891 # Is it a Oxygen atom in Carboxyl group?
2892 #
2893 sub IsCarboxylOxygen {
2894   my($This) = @_;
2895 
2896   return $This->_MatchCarboxylateAndOrCarboxylOxygen('Carboxyl');
2897 }
2898 
2899 # Is it a Carbon atom in Carboxyl group?
2900 #
2901 # Carboxyl group definition: R-C(=O)-OH
2902 #
2903 sub IsCarboxylCarbon {
2904   my($This) = @_;
2905 
2906   return $This->_MatchCarboxylateAndOrCarboxylCarbon('Carboxyl');
2907 }
2908 
2909 # Match Carboxylate and/or Carboxyl oxygen...
2910 #
2911 sub _MatchCarboxylateAndOrCarboxylOxygen {
2912   my($This, $Mode) = @_;
2913 
2914   # Is it Oxygen?
2915   if (!$This->IsOxygen()) {
2916     return 0;
2917   }
2918 
2919   # Is it connected to a Carboxylate Carbon?
2920   my($AtomNeighbor);
2921 
2922   for $AtomNeighbor ($This->GetNonHydrogenAtomNeighbors()) {
2923     if ($AtomNeighbor->_MatchCarboxylateAndOrCarboxylCarbon($Mode)) {
2924       return 1;
2925     }
2926   }
2927 
2928   return 0;
2929 }
2930 
2931 # Match Carboxylate and Carboxyl Carbon
2932 #
2933 # Carboxylate group definition: R-C(=O)-O-
2934 # Carboxyl group definition: R-C(=O)-OH
2935 #
2936 #   where:
2937 #      . R = Hydrogens or groups of atoms attached through Carbon
2938 #
2939 sub _MatchCarboxylateAndOrCarboxylCarbon {
2940   my($This, $Mode) = @_;
2941 
2942   # Is this atom in a molecule?
2943   if (!$This->HasProperty('Molecule')) {
2944     return 0;
2945   }
2946 
2947   # Is it Carbon?
2948   if (!$This->IsCarbon()) {
2949     return 0;
2950   }
2951 
2952   # Match atom neighborhood...
2953   my($CentralAtomSpec, @NbrAtomSpecsRef, @NbrBondSpecsRef, @NbrOfNbrAtomSpecsRef);
2954 
2955   $CentralAtomSpec = 'C.X3.BO4,C.X2.BO3';
2956   MODE: {
2957     if ($Mode =~ /^Carboxylate$/i) {
2958       @NbrAtomSpecsRef = ('C,H', 'O', 'O.X1.FC-1');
2959       last MODE;
2960     }
2961     if ($Mode =~ /^Carboxyl$/i) {
2962       @NbrAtomSpecsRef = ('C,H', 'O', 'O.X1.FC0');
2963       last MODE;
2964     }
2965     if ($Mode =~ /^CarboxylateOrCarboxyl$/i) {
2966       @NbrAtomSpecsRef = ('C,H', 'O', 'O.X1.FC-1,O.X1.FC0');
2967       last MODE;
2968     }
2969     carp "Warning: ${ClassName}->_MatchCarboxylateAndCarboxylCarbon.: Unknown mode $Mode...";
2970     return 0;
2971   }
2972   @NbrBondSpecsRef = ('-', '=', '-');
2973   @NbrOfNbrAtomSpecsRef = ('C,H', 'C', 'C');
2974 
2975   if ($This->DoesAtomNeighborhoodMatch($CentralAtomSpec, \@NbrAtomSpecsRef, \@NbrBondSpecsRef, \@NbrOfNbrAtomSpecsRef)) {
2976     return 1;
2977   }
2978 
2979   return 0;
2980 }
2981 
2982 # Is it a Oxygen atom in Phosphate group?
2983 #
2984 sub IsPhosphateOxygen {
2985   my($This) = @_;
2986 
2987   # Is it Oxygen?
2988   if (!$This->IsOxygen()) {
2989     return 0;
2990   }
2991 
2992   # Is it connected to a Phosphate Phosphorus?
2993   my($AtomNeighbor);
2994 
2995   for $AtomNeighbor ($This->GetNonHydrogenAtomNeighbors()) {
2996     if ($AtomNeighbor->IsPhosphatePhosphorus()) {
2997       return 1;
2998     }
2999   }
3000 
3001   return 0;
3002 }
3003 
3004 # Is it a Phosphorus atom in Phosphate group?
3005 #
3006 # Phosphate group definition: AO-(O=)P(-OA)-OA
3007 #
3008 #   where:
3009 #      . A = Any Groups of atoms including hydrogens
3010 #
3011 sub IsPhosphatePhosphorus {
3012   my($This) = @_;
3013 
3014   # Is this atom in a molecule?
3015   if (!$This->HasProperty('Molecule')) {
3016     return 0;
3017   }
3018 
3019   # Is it Phosphorus?
3020   if (!$This->IsPhosphorus()) {
3021     return 0;
3022   }
3023 
3024   # Match atom neighborhood...
3025   my($CentralAtomSpec, @NbrAtomSpecsRef, @NbrBondSpecsRef, @NbrOfNbrAtomSpecsRef);
3026 
3027   $CentralAtomSpec = 'P.X4.BO5';
3028   @NbrAtomSpecsRef = ('O', 'O', 'O', 'O');
3029   @NbrBondSpecsRef = ('-', '=', '-', '-');
3030   @NbrOfNbrAtomSpecsRef = (undef, undef, undef, undef);
3031 
3032   if ($This->DoesAtomNeighborhoodMatch($CentralAtomSpec, \@NbrAtomSpecsRef, \@NbrBondSpecsRef, \@NbrOfNbrAtomSpecsRef)) {
3033     return 1;
3034   }
3035 
3036   return 0;
3037 }
3038 
3039 
3040 # Match central atom and its neighborhood using specified atom and bonds specifications...
3041 #
3042 # Let:
3043 #   AS = Atom symbol corresponding to element symbol, atomic number (#n) or any
3044 #        atom (A)
3045 #
3046 #   X<n>   = Number of non-hydrogen atom neighbors or heavy atoms attached to atom
3047 #   T<n>   = Total number of atom neighbors including implcit and explicit hydrogens
3048 #   BO<n> = Sum of bond orders to non-hydrogen atom neighbors or heavy atoms attached to atom
3049 #   LBO<n> = Largest bond order of non-hydrogen atom neighbors or heavy atoms attached to atom
3050 #   SB<n> = Number of single bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
3051 #   TSB<n> = Total number of single bonds to atom neighbors including implcit and explicit hydrogens
3052 #   DB<n> = Number of double bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
3053 #   TB<n> = Number of triple bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
3054 #   H<n>   = Number of implicit and explicit hydrogens for atom
3055 #   Ar     = Aromatic annotation indicating whether atom is aromatic
3056 #   RA or RA<n>  = Ring atom annotation indicating whether atom is a ring
3057 #   TR<n>  = Total number of rings containing atom
3058 #   FC<+n/-n> = Formal charge assigned to atom
3059 #   MN<n> = Mass number indicating isotope other than most abundant isotope
3060 #   SM<n> = Spin multiplicity of atom. Possible values: 1 (singlet), 2 (doublet) or 3 (triplet)
3061 #
3062 # Then:
3063 #
3064 #   Atom specification corresponds to:
3065 #
3066 #     AS.X<n>.T<n>.BO<n>.LBO<n>.<SB><n>.TSB<n>.<DB><n>.<TB><n>.H<n>.Ar.RA<n>.TR<n>FC<+n/-n>.MN<n>.SM<n>
3067 #
3068 # Except for AS which is a required atomic invariant in atom specification, all other atomic invariants are
3069 # optional. For an atom specification to match an atom, the values of all specified atomic invariants must
3070 # match. Exclamation in from of atomic invariant can be used to negate its effect during the match.
3071 #
3072 # A comma delimited atom specification string is used to match any one of the specifed atom specification.
3073 #
3074 # Notes:
3075 #   . During atom specification match to an atom, the first atomic invariant is always assumed to
3076 #     atom symbol.
3077 #   . Atom match specfication is based on AtomicInvariantAtomTypes implemented in
3078 #     AotmTypes::AtomicInvariantAtomType.pm module
3079 #
3080 # Examples:
3081 #     . ('N', 'N', 'N')
3082 #     . ('N.FC0', 'N.FC0', 'N,N.FC+1.H1')
3083 #     . ('N.H2', 'N.H2', 'N.H1')
3084 #     . ('C,N', '!N', '!H')
3085 #     . ('C,N', 'N.Ar', 'N.R5')
3086 #
3087 # Let:
3088 #   -|1|s|Single = Single bond
3089 #   =|2|d|Double = Double bond
3090 #   #|3|t|Triple  = Triple bond
3091 #   :|1.5|a|Ar|Aromatic = Aromatic bond
3092 #
3093 #   @|RB|Ring = Ring bond
3094 #   ~|*|Any = Any bond
3095 #
3096 # Then:
3097 #
3098 #   Bond specification corresponds to:
3099 #
3100 #     -.:
3101 #     =.@
3102 #     Double.Aromatic
3103 #
3104 # For a bond specification to match bond between two atoms, the values of all specified bond symbols must
3105 # match. Exclamation in from of bond symbol can be used to negate its effect during the match.
3106 #
3107 # A comma delimited bond specification string is used to match any one of the specifed atom specification.
3108 #
3109 sub DoesAtomNeighborhoodMatch {
3110   my($CentralAtom, $CentralAtomSpec, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef) = @_;
3111   my($NumOfNbrAtomSpecs, $NumOfNbrBondSpecs, $NumOfNbrOfNbrAtomSpecs);
3112 
3113   # Is this atom in a molecule?
3114   if (!$CentralAtom->HasProperty('Molecule')) {
3115     return 0;
3116   }
3117 
3118   $NumOfNbrAtomSpecs = defined $NbrAtomSpecsRef ? scalar @{$NbrAtomSpecsRef} : 0;
3119   $NumOfNbrBondSpecs = defined $NbrBondSpecsRef ? scalar @{$NbrBondSpecsRef} : 0;
3120   $NumOfNbrOfNbrAtomSpecs = defined $NbrOfNbrAtomSpecsRef ? scalar @{$NbrOfNbrAtomSpecsRef} : 0;
3121 
3122   # Validate number of specifications...
3123   if ($NumOfNbrBondSpecs && ($NumOfNbrAtomSpecs != $NumOfNbrBondSpecs)) {
3124     carp "Warning: ${ClassName}->DoesAtomNeighborhoodMatch: Number of specified central atom, $NumOfNbrAtomSpecs, and bond, $NumOfNbrBondSpecs, specifications must be same; No neighborhood match performed ...";
3125     return 0;
3126   }
3127 
3128   if ($NumOfNbrOfNbrAtomSpecs && ($NumOfNbrOfNbrAtomSpecs != $NumOfNbrAtomSpecs)) {
3129     carp "Warning: ${ClassName}->DoesAtomNeighborhoodMatch: Number of specified central atom, $NumOfNbrAtomSpecs, and neighbor of neighbor atoms specifications, $NumOfNbrOfNbrAtomSpecs, must be same; No neighborhood match performed ...";
3130     return 0;
3131   }
3132 
3133   # Sort atom and bond specifications in terms of being most specific to least specific..
3134   ($NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef) = $CentralAtom->_SortSpecificationsForAtomNeighborhoodMatch($NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef);
3135 
3136   # Does central atom specification match?
3137   if (!$CentralAtom->_DoesAtomSpecificationMatch($CentralAtomSpec)) {
3138     return 0;
3139   }
3140 
3141   # No neighbors to match...
3142   if (!$NumOfNbrAtomSpecs) {
3143     return 1;
3144   }
3145 
3146   # Match neighbors...
3147   my($NbrSpecsMatched, $NbrSpecCount, $NbrSpecMatchCount, %NbrSpecAlreadyMatchedMap);
3148 
3149   $NbrSpecCount = $NumOfNbrAtomSpecs;
3150   $NbrSpecMatchCount = 0;
3151 
3152   %NbrSpecAlreadyMatchedMap = ();
3153   ($NbrSpecsMatched, $NbrSpecMatchCount) = $CentralAtom->_MatchAtomNeighborhoodUsingAtomBondSpecs($NbrSpecCount, $NbrSpecMatchCount, \%NbrSpecAlreadyMatchedMap, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef);
3154 
3155   if ($NbrSpecsMatched) {
3156     # It's match...
3157     return 1;
3158   }
3159 
3160   # Match central atom's missing hydrogens with any unmatched atom
3161   # and bond specifications...
3162   #
3163   ($NbrSpecsMatched, $NbrSpecMatchCount) = $CentralAtom->_MatchAtomNeighborhoodUsingMissingHydrogens($NbrSpecCount, $NbrSpecMatchCount, \%NbrSpecAlreadyMatchedMap, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef);
3164 
3165   if ($NbrSpecsMatched) {
3166     # It's match...
3167     return 1;
3168   }
3169 
3170   # No match...
3171   return 0;
3172 }
3173 
3174 # Match central atom neighborhood atom and bond specifications...
3175 #
3176 sub _MatchAtomNeighborhoodUsingAtomBondSpecs {
3177   my($CentralAtom, $NbrSpecCount, $NbrSpecMatchCount, $NbrSpecAlreadyMatchedRef, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef) = @_;
3178   my($Index, $NbrAtom, $NbrAtomSpec, $NbrBondSpec, $NbrOfNbrAtom, $NbrOfNbrAtomSpec, $MatchNbrOfNbrAtomSpecs, $NbrSpecsMatched);
3179 
3180   $MatchNbrOfNbrAtomSpecs = (defined $NbrOfNbrAtomSpecsRef && scalar @{$NbrOfNbrAtomSpecsRef}) ? 1 : 0;
3181 
3182   $NbrSpecsMatched = 0;
3183 
3184   # Match central atom's  immediate neighbors atom and bond specifications...
3185   NBRATOM:  for $NbrAtom ($CentralAtom->GetNeighbors()) {
3186     NBRATOMSPEC: for $Index (0 .. ($NbrSpecCount - 1)) {
3187       if (exists $NbrSpecAlreadyMatchedRef->{$Index}) {
3188         next NBRATOMSPEC;
3189       }
3190       $NbrAtomSpec = $NbrAtomSpecsRef->[$Index];
3191       $NbrBondSpec = $NbrBondSpecsRef->[$Index];
3192 
3193       $NbrOfNbrAtomSpec = $MatchNbrOfNbrAtomSpecs ? $NbrOfNbrAtomSpecsRef->[$Index] : undef;
3194 
3195       # Match neighbor atom specification...
3196       if (!$NbrAtom->_DoesAtomSpecificationMatch($NbrAtomSpec)) {
3197         next NBRATOMSPEC;
3198       }
3199 
3200       # Match central atom to neighbor atom bond specification...
3201       if (!$CentralAtom->_DoesBondSpecificationMatch($NbrAtom, $NbrBondSpec)) {
3202         next NBRATOMSPEC;
3203       }
3204 
3205       # Match any neighbor of neighbor atom specifications...
3206       if (defined $NbrOfNbrAtomSpec) {
3207         # Go over the neighbors of central atom skipping the central atom...
3208         for $NbrOfNbrAtom ($NbrAtom->GetNeighbors($CentralAtom)) {
3209           if (!$NbrOfNbrAtom->_DoesAtomSpecificationMatch($NbrOfNbrAtomSpec)) {
3210             next NBRATOMSPEC;
3211           }
3212         }
3213       }
3214 
3215       # It's a match for a neighbor atom specification...
3216       $NbrSpecAlreadyMatchedRef->{$Index} = $Index;
3217       $NbrSpecMatchCount++;
3218 
3219       if ($NbrSpecMatchCount == $NbrSpecCount) {
3220         # It's match...
3221         $NbrSpecsMatched = 1;
3222         last NBRATOM;
3223       }
3224       # Match next neighbor atom...
3225       next NBRATOM;
3226     }
3227   }
3228   return ($NbrSpecsMatched, $NbrSpecMatchCount);
3229 }
3230 
3231 # Match central atom's missing hydrogens with any unmatched atom and bond
3232 # specifications...
3233 #
3234 sub _MatchAtomNeighborhoodUsingMissingHydrogens {
3235   my($CentralAtom, $NbrSpecCount, $NbrSpecMatchCount, $NbrSpecAlreadyMatchedRef, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef) = @_;
3236   my($Index, $NbrAtom, $NbrAtomSpec, $NbrBondSpec, $NumOfMissingHydrogens, $MissingHydrogensIndex, $NbrSpecsMatched, $AtomSpecMatched, $AtomSpec, $AtomSymbol);
3237 
3238   $NbrSpecsMatched = 0;
3239 
3240   $NumOfMissingHydrogens = $CentralAtom->GetNumOfMissingHydrogens();
3241   if (($NbrSpecCount - $NbrSpecMatchCount) > $NumOfMissingHydrogens) {
3242     # It won't match...
3243     return ($NbrSpecsMatched, $NbrSpecMatchCount);
3244   }
3245 
3246   MISSINGHYDROGENNBR: for $MissingHydrogensIndex (0 .. ($NumOfMissingHydrogens - 1)) {
3247     NBRATOMSPEC: for $Index (0 .. ($NbrSpecCount - 1)) {
3248       if (exists $NbrSpecAlreadyMatchedRef->{$Index}) {
3249         next NBRATOMSPEC;
3250       }
3251       $NbrAtomSpec = $NbrAtomSpecsRef->[$Index];
3252       $NbrBondSpec = $NbrBondSpecsRef->[$Index];
3253 
3254       $NbrAtomSpec =~ s/ //g;
3255 
3256       # Match neighbor atom specification hydrogen atom symbol...
3257       $AtomSpecMatched = 0;
3258       ATOMSPEC: for $AtomSpec (split /\,/, $NbrAtomSpec) {
3259         ($AtomSymbol) = split /\./, $AtomSpec;
3260         if ($AtomSymbol =~ /^(H|A|\*)$/i) {
3261           $AtomSpecMatched = 1;
3262           last ATOMSPEC;
3263         }
3264       }
3265       if (!$AtomSpecMatched) {
3266         next NBRATOMSPEC;
3267       }
3268 
3269       # Match neighbor atom bond specification to singal bond...
3270       if (defined $NbrBondSpec) {
3271         $NbrBondSpec =~ s/ //g;
3272         if ($NbrBondSpec !~ /^(-|1|s|Single|\~|\*|Any)/i) {
3273           next NBRATOMSPEC;
3274         }
3275       }
3276 
3277       # It's a match for a neighbor atom specification...
3278       $NbrSpecAlreadyMatchedRef->{$Index} = $Index;
3279       $NbrSpecMatchCount++;
3280 
3281       if ($NbrSpecMatchCount == $NbrSpecCount) {
3282         # It's match...
3283         $NbrSpecsMatched = 1;
3284         last MISSINGHYDROGENNBR;
3285       }
3286       # Match next missing hydrogen neighbor...
3287       next MISSINGHYDROGENNBR;
3288     }
3289   }
3290 
3291   return ($NbrSpecsMatched, $NbrSpecMatchCount);
3292 }
3293 
3294 # Sort atom and bond specifications base on neighbor atom specifications going
3295 # from most to least specific atom specifications.
3296 #
3297 # Atom specifications are sorted at the following two levels:
3298 #
3299 #   o By atom specification count with in each specification going from most specific
3300 #      to least specific, where count is determined by the number of "," in each
3301 #      specification. Wild card containing specifications are considered least specific
3302 #      and end up at the end of the sorted list.
3303 #   o By atomic invariant count with in each sorted list going from most specific to
3304 #      least specific, where count is determined by the number of "." in each atom
3305 #      specification.
3306 #
3307 #A single atom specification,
3308 # without any commas in atom specification, is is considered most specific...
3309 #
3310 sub _SortSpecificationsForAtomNeighborhoodMatch {
3311   my($This, $NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef) = @_;
3312   my($Index, $NeedToSort, $NumOfNbrAtomSpecs, $NbrAtomSpecCount,  $NbrAtomSpecAtomicInvarintCount, $NbrAtomSpecToMatch, $NbrAtomSpec, $FirstAtomicInvariant, $WildCardInNbrAtomSpec, @NbrAtomSpecs, @NbrAtomSpecAtomicInvariants, @SortedNbrAtomSpecs, @SortedNbrBondSpecs, @SortedNbrOfNbrAtomSpecs, %NbrAtomSpecDataMap);
3313 
3314   $NumOfNbrAtomSpecs = defined $NbrAtomSpecsRef ? scalar @{$NbrAtomSpecsRef} : 0;
3315 
3316   # Figure out whether sorting is necessary...
3317   $NeedToSort = 0;
3318   if ($NumOfNbrAtomSpecs > 1) {
3319     ATOMSPEC: for $NbrAtomSpecToMatch (@{$NbrAtomSpecsRef}) {
3320       if ($NbrAtomSpecToMatch =~ /(,|\.|A|\*)/i) {
3321         $NeedToSort = 1;
3322         last ATOMSPEC;
3323       }
3324     }
3325   }
3326   if (!$NeedToSort) {
3327     # Nothing to do...
3328     return ($NbrAtomSpecsRef, $NbrBondSpecsRef, $NbrOfNbrAtomSpecsRef);
3329   }
3330 
3331   %NbrAtomSpecDataMap = ();
3332 
3333   for $Index (0 .. ($NumOfNbrAtomSpecs - 1)) {
3334     $NbrAtomSpecToMatch = $NbrAtomSpecsRef->[$Index];
3335     $NbrAtomSpecToMatch =~ s/ //g;
3336 
3337     @NbrAtomSpecs = split /\,/, $NbrAtomSpecToMatch;
3338     $NbrAtomSpecCount = scalar @NbrAtomSpecs;
3339 
3340     # Does neighbor specification contains a wild card in atom symbol specification?
3341     #
3342     if ($NbrAtomSpecToMatch =~ /(A|\*)/i) {
3343       $WildCardInNbrAtomSpec = 0;
3344       NBRATOMSPEC: for $NbrAtomSpec (@NbrAtomSpecs) {
3345         ($FirstAtomicInvariant) = split /\./, $NbrAtomSpec;
3346         if ($FirstAtomicInvariant =~ /^!/) {
3347           $FirstAtomicInvariant =~ s/^!//;
3348         }
3349         $WildCardInNbrAtomSpec = $This->_IsWildCardAtomSymbolAtomicInvariant($FirstAtomicInvariant);
3350         if ($WildCardInNbrAtomSpec) {
3351           last NBRATOMSPEC;
3352         }
3353       }
3354       if ($WildCardInNbrAtomSpec) {
3355         # Set NbrAtomSpecCount arbitrarily high to make the spec containing wild
3356         # card last on the sorted list while maintaining its original order in the list...
3357         $NbrAtomSpecCount = 999;
3358       }
3359     }
3360 
3361     if (!exists $NbrAtomSpecDataMap{$NbrAtomSpecCount}) {
3362       %{$NbrAtomSpecDataMap{$NbrAtomSpecCount}} = ();
3363     }
3364 
3365     # Use first NbrAtomSpec available in @NbrAtomSpecs to determine atomic invariant count
3366     # with in each NbrAtomSpecToMatch, as @NbrAtomSpecs derived from $NbrAtomSpecToMatch
3367     # simply corresponds to a list of possible matches...
3368     #
3369     ($NbrAtomSpec) = @NbrAtomSpecs;
3370     @NbrAtomSpecAtomicInvariants = split /\./, $NbrAtomSpec;
3371     $NbrAtomSpecAtomicInvarintCount = scalar @NbrAtomSpecAtomicInvariants;
3372 
3373     if (!exists $NbrAtomSpecDataMap{$NbrAtomSpecCount}{$NbrAtomSpecAtomicInvarintCount}) {
3374       @{$NbrAtomSpecDataMap{$NbrAtomSpecCount}{$NbrAtomSpecAtomicInvarintCount}} = ();
3375     }
3376     push @{$NbrAtomSpecDataMap{$NbrAtomSpecCount}{$NbrAtomSpecAtomicInvarintCount}}, $Index;
3377 
3378   }
3379 
3380   @SortedNbrAtomSpecs = (); @SortedNbrBondSpecs = ();
3381   @SortedNbrOfNbrAtomSpecs = ();
3382 
3383   for $NbrAtomSpecCount ( sort { $a <=> $b } keys %NbrAtomSpecDataMap) {
3384     for $NbrAtomSpecAtomicInvarintCount ( sort { $b <=> $a } keys %{$NbrAtomSpecDataMap{$NbrAtomSpecCount}}) {
3385       for $Index (@{$NbrAtomSpecDataMap{$NbrAtomSpecCount}{$NbrAtomSpecAtomicInvarintCount}}) {
3386         push @SortedNbrAtomSpecs, $NbrAtomSpecsRef->[$Index];
3387         if (defined $NbrBondSpecsRef) {
3388           push @SortedNbrBondSpecs, $NbrBondSpecsRef->[$Index];
3389         }
3390         if (defined $NbrOfNbrAtomSpecsRef) {
3391           push @SortedNbrOfNbrAtomSpecs, $NbrOfNbrAtomSpecsRef->[$Index];
3392         }
3393       }
3394     }
3395   }
3396 
3397   return (\@SortedNbrAtomSpecs, defined $NbrBondSpecsRef ? \@SortedNbrBondSpecs : undef, defined $NbrOfNbrAtomSpecsRef ? \@SortedNbrOfNbrAtomSpecs : undef);
3398 }
3399 
3400 # Check whether atom matches supported atom specification...
3401 #
3402 sub _DoesAtomSpecificationMatch {
3403   my($This, $AtomSpecificationToMatch) = @_;
3404   my($AtomSpecification, $AtomicInvariant, $AtomSpecificationMatched, $AtomicInvariantMatched, $FirstMatch);
3405 
3406   # Anything to match...
3407   if (!(defined($AtomSpecificationToMatch) && $AtomSpecificationToMatch)) {
3408     return 1;
3409   }
3410 
3411   # Take out any spaces...
3412   $AtomSpecificationToMatch =~ s/ //g;
3413 
3414   # Match specified atom specifications. For multiple atom specifications in a comma delimited string,
3415   # only one atom specification needs to match for a successful match. It's up to the caller to make
3416   # sure that the specificaton list is ordered from least to most specific atom specification...
3417   #
3418   for $AtomSpecification (split /\,/, $AtomSpecificationToMatch) {
3419     $AtomSpecificationMatched = 1;
3420     $FirstMatch = 1;
3421 
3422     # Match all atom symbol atomic invariants...
3423     ATOMICINVARIANT: for $AtomicInvariant (split /\./, $AtomSpecification) {
3424       if ($FirstMatch) {
3425         # Match atom symbol atomic invariant...
3426         $FirstMatch = 0;
3427         $AtomicInvariantMatched = $This->_MatchAtomSymbolAtomicInvariant($AtomicInvariant);
3428       }
3429       else {
3430         # Match non atom symbol atomic invariant...
3431         $AtomicInvariantMatched = $This->_MatchNonAtomSymbolAtomicInvariant($AtomicInvariant);
3432       }
3433 
3434       if (!$AtomicInvariantMatched) {
3435         # No need to match other atomic invariants...
3436         $AtomSpecificationMatched = 0;
3437         last ATOMICINVARIANT;
3438       }
3439     }
3440 
3441     if ($AtomSpecificationMatched) {
3442       # No need to match other atom specifications...
3443       return 1;
3444     }
3445   }
3446 
3447   # Nothing matched...
3448   return 0;
3449 }
3450 
3451 # Check whether atom matches atom symbol atomic invariant...
3452 #
3453 sub _MatchAtomSymbolAtomicInvariant {
3454   my($This, $AtomicInvariant) = @_;
3455   my($NegateMatch, $Status, $AtomicNumber);
3456 
3457   $Status = 0;
3458   $NegateMatch = 0;
3459 
3460   # Does match needs to be negated?
3461   if ($AtomicInvariant =~ /^!/) {
3462     $NegateMatch = 1;
3463     $AtomicInvariant =~ s/^!//;
3464   }
3465 
3466   ATOMICINVARIANT: {
3467     # Any atom match...
3468     if ($This->_IsWildCardAtomSymbolAtomicInvariant($AtomicInvariant)) {
3469       $Status = 1;
3470       last ATOMICINVARIANT;
3471     }
3472 
3473     # Atomic number match...
3474     if ($AtomicInvariant =~ /^#/) {
3475       $AtomicNumber = $AtomicInvariant; $AtomicNumber =~ s/^#//;
3476       $Status = ($This->{AtomicNumber} == $AtomicNumber) ? 1 : 0;
3477       last ATOMICINVARIANT;
3478     }
3479 
3480     # Atom symbol match...
3481     $Status = ($This->{AtomSymbol} =~ /^$AtomicInvariant$/i) ? 1 : 0;
3482   }
3483 
3484   if ($NegateMatch) {
3485     $Status = $Status ? 0 : 1;
3486   }
3487 
3488   return $Status;
3489 }
3490 
3491 # Is it a wild card atom symbol atomic invariant?
3492 #
3493 sub _IsWildCardAtomSymbolAtomicInvariant {
3494   my($This, $AtomicInvariant) = @_;
3495 
3496   return ($AtomicInvariant =~ /^(A|\*)$/i) ? 1 : 0;
3497 }
3498 
3499 # Check whether atom matches non atom symbol atomic invariants...
3500 #
3501 sub _MatchNonAtomSymbolAtomicInvariant {
3502   my($This, $AtomicInvariant) = @_;
3503   my($NegateMatch, $Status, $Name, $Value, $UnknownName);
3504 
3505   ($Status, $NegateMatch, $UnknownName) = ('0') x 3;
3506 
3507   # Does match needs to be negated?
3508   if ($AtomicInvariant =~ /^!/) {
3509     $NegateMatch = 1;
3510     $AtomicInvariant =~ s/^!//;
3511   }
3512 
3513   # Extract atomic invariant name and any value...
3514   if ($AtomicInvariant =~ /[0-9\*]+/) {
3515     ($Name, $Value) = $AtomicInvariant =~ /^([a-zA-Z]+)([0-9\-\+\*\>\<\=]+)$/;
3516   }
3517   else {
3518     ($Name, $Value) = ($AtomicInvariant, undef);
3519   }
3520 
3521   NAME: {
3522     # Match number of non-hydrogen atom neighbors
3523     if ($Name =~ /^X$/i) {
3524       $Status = (defined($Value) && $This->GetNumOfNonHydrogenAtomNeighbors() == $Value) ? 1 : 0;
3525       last NAME;
3526     }
3527 
3528     # Match total number of atom neighbors including missing hydrogens...
3529     if ($Name =~ /^T$/i) {
3530       $Status = (defined($Value) && ($This->GetNumOfNonHydrogenAtomNeighbors() + $This->GetNumOfHydrogens()) == $Value) ? 1 : 0;
3531       last NAME;
3532     }
3533 
3534     # Match formal charge...
3535     if ($Name =~ /^FC$/i) {
3536       my $FormalCharge = $This->GetFormalCharge();
3537       $Status = $This->_MatchNonAtomSymbolAtomicInvariantValue($FormalCharge, $Value);
3538       last NAME;
3539     }
3540 
3541     # Match aromatic annotation indicating whether atom is aromatic...
3542     if ($Name =~ /^Ar$/i) {
3543       $Status = $This->IsAromatic() ? 1 : 0;
3544       last NAME;
3545     }
3546 
3547     # Match number of implicit and explicit hydrogens...
3548     if ($Name =~ /^H$/i) {
3549       $Status = (defined($Value) && ($This->GetNumOfHydrogens() == $Value)) ? 1 : 0;
3550       last NAME;
3551     }
3552 
3553     # Match ring atom annotation indicating whether atom is in ring...
3554     if ($Name =~ /^RA$/i) {
3555       $Status = defined($Value) ? $This->IsInRingOfSize($Value) : ($This->IsInRing() ? 1 : 0);
3556       last NAME;
3557     }
3558 
3559     # Match number of rings for atom..
3560     if ($Name =~ /^TR$/i) {
3561       $Status = (defined($Value) && ($Value == $This->GetNumOfRings())) ? 1 : 0;
3562       last NAME;
3563     }
3564 
3565     # Match sum of bond orders to non-hydrogen atom neighbors...
3566     if ($Name =~ /^BO$/i) {
3567       $Status = (defined($Value) && $This->GetSumOfBondOrdersToNonHydrogenAtoms() == $Value) ? 1 : 0;
3568       last NAME;
3569     }
3570 
3571     # Match largest bond order of non-hydrogen atom neighbors...
3572     if ($Name =~ /^LBO$/i) {
3573       $Status = (defined($Value) && $This->GetLargestBondOrderToNonHydrogenAtoms() == $Value) ? 1 : 0;
3574       last NAME;
3575     }
3576 
3577     # Match number of single bonds to non-hydrogen atom neighbors...
3578     if ($Name =~ /^SB$/i) {
3579       $Status = (defined($Value) && $This->GetNumOfSingleBondsToNonHydrogenAtoms() == $Value) ? 1 : 0;
3580       last NAME;
3581     }
3582 
3583     # Match total number of single bonds to atom neighbors including missing and explicit hydrogens...
3584     if ($Name =~ /^TSB$/i) {
3585       $Status = (defined($Value) && ($This->GetNumOfSingleBondsToNonHydrogenAtoms() + $This->GetNumOfHydrogens()) == $Value) ? 1 : 0;
3586       last NAME;
3587     }
3588 
3589     # Match number of double bonds to non-hydrogen atom neighbors...
3590     if ($Name =~ /^DB$/i) {
3591       $Status = (defined($Value) && $This->GetNumOfDoubleBondsToNonHydrogenAtoms() == $Value) ? 1 : 0;
3592       last NAME;
3593     }
3594 
3595     # Match number of triple bonds to non-hydrogen atom neighbors...
3596     if ($Name =~ /^TB$/i) {
3597       $Status = (defined($Value) && $This->GetNumOfTripleBondsToNonHydrogenAtoms() == $Value) ? 1 : 0;
3598       last NAME;
3599     }
3600 
3601     # Match number of aromatic bonds to non-hydrogen atom neighbors...
3602     if ($Name =~ /^AB$/i) {
3603       $Status = (defined($Value) && $This->GetNumOfAromaticBondsToNonHydrogenAtoms() == $Value) ? 1 : 0;
3604       last NAME;
3605     }
3606 
3607 
3608     # Match mass number indicating isotope other than most abundant isotope...
3609     if ($Name =~ /^MN$/i) {
3610       $Status = (defined($Value) && $This->GetMassNumber() == $Value) ? 1 : 0;
3611       last NAME;
3612     }
3613 
3614     # Match spin multiplicity...
3615     if ($Name =~ /^SM$/i) {
3616       my $SpinMultiplicity = $This->GetSpinMultiplicity();
3617       if (!defined $SpinMultiplicity) { $SpinMultiplicity = 0; }
3618       $Status = (defined($Value) && defined($SpinMultiplicity) && $Value == $SpinMultiplicity) ? 1 : 0;
3619       last NAME;
3620     }
3621 
3622     $UnknownName = 1;
3623     carp "Warning: ${ClassName}->_MatchNonAtomSymbolAtomicInvariant: Unknown atomic invariant $AtomicInvariant...";
3624   }
3625 
3626   if (!$UnknownName) {
3627     if ($NegateMatch) {
3628       $Status = $Status ? 0 : 1;
3629     }
3630   }
3631 
3632   return $Status;
3633 }
3634 
3635 # Match atomic invariant value...
3636 #
3637 # Specified value format:
3638 #   . +* : Any positive value
3639 #   . -* : Any negative value
3640 #   . >ValidNumber or >=ValidNumber
3641 #   . <ValidNumber or <=ValidNumber
3642 #   . Any valid number
3643 #
3644 sub _MatchNonAtomSymbolAtomicInvariantValue {
3645   my($This, $TargetValue, $SpecifiedValue) = @_;
3646   my($Status);
3647 
3648   $Status = 0;
3649 
3650   if (!(defined($TargetValue) && defined($SpecifiedValue))) {
3651     return $Status;
3652   }
3653 
3654   VALUE: {
3655     if ($SpecifiedValue =~ /^\+\*/) {
3656       $Status = ($TargetValue > 0) ? 1 : 0;
3657       last VALUE;
3658     }
3659     if ($SpecifiedValue =~ /^\-\*/) {
3660       $Status = ($TargetValue < 0) ? 1 : 0;
3661       last VALUE;
3662     }
3663     if ($SpecifiedValue =~ /^>/) {
3664       if ($SpecifiedValue =~ /^>=/) {
3665         $SpecifiedValue =~ s/^>=//;
3666         $Status = ($SpecifiedValue >= $TargetValue) ? 1 : 0;
3667       }
3668       else {
3669         $SpecifiedValue =~ s/^>//;
3670         $Status = ($SpecifiedValue > $TargetValue) ? 1 : 0;
3671       }
3672       last VALUE;
3673     }
3674     if ($SpecifiedValue =~ /^</) {
3675       if ($SpecifiedValue =~ /^<=/) {
3676         $SpecifiedValue =~ s/^<=//;
3677         $Status = ($SpecifiedValue <= $TargetValue) ? 1 : 0;
3678       }
3679       else {
3680         $SpecifiedValue =~ s/^<//;
3681         $Status = ($SpecifiedValue < $TargetValue) ? 1 : 0;
3682       }
3683       last VALUE;
3684     }
3685     # Default is do perform an equality match...
3686     $Status = ($SpecifiedValue == $TargetValue) ? 1 : 0;
3687   }
3688 
3689   return $Status;
3690 }
3691 
3692 # Check whether atoms match  bond specifications...
3693 #
3694 sub _DoesBondSpecificationMatch {
3695   my($This, $BondedAtom, $BondSpecificationToMatch) = @_;
3696   my($BondSpecification, $BondSymbolSpecification, $BondSpecificationMatched);
3697 
3698   # Anything to match...
3699   if (!(defined($BondSpecificationToMatch) && $BondSpecificationToMatch)) {
3700     return 1;
3701   }
3702 
3703   # Take out any spaces...
3704   $BondSpecificationToMatch =~ s/ //g;
3705 
3706   # Match specified bond specifications. For multiple bond specifications in a comma delimited string,
3707   # only one bond specification needs to match for a successful match...
3708   #
3709   for $BondSpecification (split /\,/, $BondSpecificationToMatch) {
3710     $BondSpecificationMatched = 1;
3711 
3712     # Match all specified bond symbol specifications...
3713     BONDSYMBOL: for $BondSymbolSpecification (split /\./, $BondSpecification) {
3714       if (!$This->_MatchBondSymbolSpecification($BondedAtom, $BondSymbolSpecification)) {
3715         # No need to match other bond symbol specifications...
3716         $BondSpecificationMatched = 0;
3717         last BONDSYMBOL;
3718       }
3719     }
3720     if ($BondSpecificationMatched) {
3721       # No need to try matching other bond specifications...
3722       return 1;
3723     }
3724   }
3725 
3726   # Nothing matched...
3727   return 0;
3728 }
3729 
3730 # Check whether atoms match  bond symbol specification...
3731 #
3732 sub _MatchBondSymbolSpecification {
3733   my($This, $BondedAtom, $BondSymbolSpecification) = @_;
3734   my($NegateMatch, $Status, $Bond, $BondSymbol, $UnknownBondSymbol);
3735 
3736   ($Status, $NegateMatch, $UnknownBondSymbol) = ('0') x 3;
3737 
3738   # Does match needs to be negated?
3739   if ($BondSymbolSpecification =~ /^!/) {
3740     $NegateMatch = 1;
3741     $BondSymbolSpecification =~ s/^!//;
3742   }
3743   $BondSymbol = $BondSymbolSpecification;
3744   $Bond = $This->GetBondToAtom($BondedAtom);
3745 
3746   BONDSYMBOL: {
3747     if ($BondSymbol =~ /^(-|1|s|Single)$/i) { $Status = $Bond->IsSingle() ? 1 : 0; last BONDSYMBOL; }
3748     if ($BondSymbol =~ /^(=|2|d|Double)$/i) { $Status = $Bond->IsDouble() ? 1 : 0; last BONDSYMBOL; }
3749     if ($BondSymbol =~ /^(#|3|t|Triple)$/i) { $Status = $Bond->IsTriple() ? 1 : 0; last BONDSYMBOL; }
3750     if ($BondSymbol =~ /^(:|a|Ar|Aromatic)$/i) { $Status = $Bond->IsAromatic() ? 1 : 0; last BONDSYMBOL; }
3751 
3752     if ($BondSymbol =~ /^(\@|RB|Ring)$/i) { $Status = $Bond->IsInRing() ? 1 : 0; last BONDSYMBOL; }
3753 
3754     if ($BondSymbol =~ /^(\~|\*|Any)$/i) { $Status = 1; last BONDSYMBOL; }
3755 
3756     $UnknownBondSymbol = 1;
3757     carp "Warning: ${ClassName}->_MatchBondSpecification: Unknown bond specification $BondSymbolSpecification...";
3758   }
3759 
3760   if (!$UnknownBondSymbol) {
3761     if ($NegateMatch) {
3762       $Status = $Status ? 0 : 1;
3763     }
3764   }
3765 
3766   return $Status;
3767 }
3768 
3769 # Is it a saturated atom?
3770 #
3771 sub IsSaturated {
3772   my($This) = @_;
3773 
3774   return !$This->IsUnsaturated();
3775 }
3776 
3777 # Is it an unsaturated atom containing at least one non-single bond?
3778 #
3779 sub IsUnsaturated {
3780   my($This) = @_;
3781   my($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfAromaticBonds);
3782 
3783   ($NumOfSingleBonds, $NumOfDoubleBonds, $NumOfTripleBonds, $NumOfAromaticBonds) = $This->GetNumOfBondTypesToNonHydrogenAtoms();
3784 
3785   return ($NumOfDoubleBonds || $NumOfTripleBonds || $NumOfAromaticBonds) ? 1 : 0;
3786 }
3787 
3788 # Is atom in a ring?
3789 #
3790 sub IsInRing {
3791   my($This) = @_;
3792 
3793   # Is this atom in a molecule?
3794   if (!$This->HasProperty('Molecule')) {
3795     return undef;
3796   }
3797   my($Molecule);
3798   $Molecule = $This->GetProperty('Molecule');
3799 
3800   return $Molecule->_IsAtomInRing($This);
3801 }
3802 
3803 # Is atom not in a ring?
3804 #
3805 sub IsNotInRing {
3806   my($This) = @_;
3807 
3808   # Is this atom in a molecule?
3809   if (!$This->HasProperty('Molecule')) {
3810     return undef;
3811   }
3812   my($Molecule);
3813   $Molecule = $This->GetProperty('Molecule');
3814 
3815   return $Molecule->_IsAtomNotInRing($This);
3816 }
3817 
3818 # Is atom only in one ring?
3819 #
3820 sub IsOnlyInOneRing {
3821   my($This) = @_;
3822 
3823   # Is this atom in a molecule?
3824   if (!$This->HasProperty('Molecule')) {
3825     return undef;
3826   }
3827   my($Molecule);
3828   $Molecule = $This->GetProperty('Molecule');
3829 
3830   return $Molecule->_IsAtomInOnlyOneRing($This);
3831 }
3832 
3833 # Is atom in a ring of specific size?
3834 #
3835 sub IsInRingOfSize {
3836   my($This, $RingSize) = @_;
3837 
3838   # Is this atom in a molecule?
3839   if (!$This->HasProperty('Molecule')) {
3840     return undef;
3841   }
3842   my($Molecule);
3843   $Molecule = $This->GetProperty('Molecule');
3844 
3845   return $Molecule->_IsAtomInRingOfSize($This, $RingSize);
3846 }
3847 
3848 # Get size of smallest ring containing the atom...
3849 #
3850 sub GetSizeOfSmallestRing {
3851   my($This) = @_;
3852 
3853   # Is this atom in a molecule?
3854   if (!$This->HasProperty('Molecule')) {
3855     return undef;
3856   }
3857   my($Molecule);
3858   $Molecule = $This->GetProperty('Molecule');
3859 
3860   return $Molecule->_GetSizeOfSmallestAtomRing($This);
3861 }
3862 
3863 # Get size of largest ring containing the atom...
3864 #
3865 sub GetSizeOfLargestRing {
3866   my($This) = @_;
3867 
3868   # Is this atom in a molecule?
3869   if (!$This->HasProperty('Molecule')) {
3870     return undef;
3871   }
3872   my($Molecule);
3873   $Molecule = $This->GetProperty('Molecule');
3874 
3875   return $Molecule->_GetSizeOfLargestAtomRing($This);
3876 }
3877 
3878 # Get number of  rings containing the atom...
3879 #
3880 sub GetNumOfRings {
3881   my($This) = @_;
3882 
3883   # Is this atom in a molecule?
3884   if (!$This->HasProperty('Molecule')) {
3885     return undef;
3886   }
3887   my($Molecule);
3888   $Molecule = $This->GetProperty('Molecule');
3889 
3890   return $Molecule->_GetNumOfAtomRings($This);
3891 }
3892 
3893 # Get number of  rings with odd size containing the atom...
3894 #
3895 sub GetNumOfRingsWithOddSize {
3896   my($This) = @_;
3897 
3898   # Is this atom in a molecule?
3899   if (!$This->HasProperty('Molecule')) {
3900     return undef;
3901   }
3902   my($Molecule);
3903   $Molecule = $This->GetProperty('Molecule');
3904 
3905   return $Molecule->_GetNumOfAtomRingsWithOddSize($This);
3906 }
3907 
3908 # Get number of  rings with even size containing the atom...
3909 #
3910 sub GetNumOfRingsWithEvenSize {
3911   my($This) = @_;
3912 
3913   # Is this atom in a molecule?
3914   if (!$This->HasProperty('Molecule')) {
3915     return undef;
3916   }
3917   my($Molecule);
3918   $Molecule = $This->GetProperty('Molecule');
3919 
3920   return $Molecule->_GetNumOfAtomRingsWithEvenSize($This);
3921 }
3922 
3923 # Get number of  rings with specified size containing the atom...
3924 #
3925 sub GetNumOfRingsWithSize {
3926   my($This, $RingSize) = @_;
3927 
3928   # Is this atom in a molecule?
3929   if (!$This->HasProperty('Molecule')) {
3930     return undef;
3931   }
3932   my($Molecule);
3933   $Molecule = $This->GetProperty('Molecule');
3934 
3935   return $Molecule->_GetNumOfAtomRingsWithSize($This, $RingSize);
3936 
3937 }
3938 
3939 # Get number of  rings with size less than specified containing the atom...
3940 #
3941 sub GetNumOfRingsWithSizeLessThan {
3942   my($This, $RingSize) = @_;
3943 
3944   # Is this atom in a molecule?
3945   if (!$This->HasProperty('Molecule')) {
3946     return undef;
3947   }
3948   my($Molecule);
3949   $Molecule = $This->GetProperty('Molecule');
3950 
3951   return $Molecule->_GetNumOfAtomRingsWithSizeLessThan($This, $RingSize);
3952 }
3953 
3954 # Get number of  rings with size greater than specified size containing the atom...
3955 #
3956 sub GetNumOfRingsWithSizeGreaterThan {
3957   my($This, $RingSize) = @_;
3958 
3959   # Is this atom in a molecule?
3960   if (!$This->HasProperty('Molecule')) {
3961     return undef;
3962   }
3963   my($Molecule);
3964   $Molecule = $This->GetProperty('Molecule');
3965 
3966   return $Molecule->_GetNumOfAtomRingsWithSizeGreaterThan($This, $RingSize);
3967 }
3968 
3969 # Get all rings an array of references to arrays containing ring atoms...
3970 #
3971 sub GetRings {
3972   my($This) = @_;
3973 
3974   # Is this atom in a molecule?
3975   if (!$This->HasProperty('Molecule')) {
3976     return undef;
3977   }
3978   my($Molecule);
3979   $Molecule = $This->GetProperty('Molecule');
3980 
3981   return $Molecule->_GetAtomRings($This);
3982 }
3983 
3984 # Get smallest ring as an array containing ring atoms...
3985 #
3986 sub GetSmallestRing {
3987   my($This) = @_;
3988 
3989   # Is this atom in a molecule?
3990   if (!$This->HasProperty('Molecule')) {
3991     return undef;
3992   }
3993   my($Molecule);
3994   $Molecule = $This->GetProperty('Molecule');
3995 
3996   return $Molecule->_GetSmallestAtomRing($This);
3997 }
3998 
3999 # Get largest ring as an array containing ring atoms...
4000 #
4001 sub GetLargestRing {
4002   my($This) = @_;
4003 
4004   # Is this atom in a molecule?
4005   if (!$This->HasProperty('Molecule')) {
4006     return undef;
4007   }
4008   my($Molecule);
4009   $Molecule = $This->GetProperty('Molecule');
4010 
4011   return $Molecule->_GetLargestAtomRing($This);
4012 }
4013 
4014 # Get odd size rings an array of references to arrays containing ring atoms...
4015 #
4016 sub GetRingsWithOddSize {
4017   my($This) = @_;
4018 
4019   # Is this atom in a molecule?
4020   if (!$This->HasProperty('Molecule')) {
4021     return undef;
4022   }
4023   my($Molecule);
4024   $Molecule = $This->GetProperty('Molecule');
4025 
4026   return $Molecule->_GetAtomRingsWithOddSize($This);
4027 }
4028 
4029 # Get even size rings an array of references to arrays containing ring atoms...
4030 #
4031 sub GetRingsWithEvenSize {
4032   my($This) = @_;
4033 
4034   # Is this atom in a molecule?
4035   if (!$This->HasProperty('Molecule')) {
4036     return undef;
4037   }
4038   my($Molecule);
4039   $Molecule = $This->GetProperty('Molecule');
4040 
4041   return $Molecule->_GetAtomRingsWithEvenSize($This);
4042 }
4043 
4044 # Get rings with specified size as an array of references to arrays containing ring atoms...
4045 #
4046 sub GetRingsWithSize {
4047   my($This, $RingSize) = @_;
4048 
4049   # Is this atom in a molecule?
4050   if (!$This->HasProperty('Molecule')) {
4051     return undef;
4052   }
4053   my($Molecule);
4054   $Molecule = $This->GetProperty('Molecule');
4055 
4056   return $Molecule->_GetAtomRingsWithSize($This, $RingSize);
4057 }
4058 
4059 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
4060 #
4061 sub GetRingsWithSizeLessThan {
4062   my($This, $RingSize) = @_;
4063 
4064   # Is this atom in a molecule?
4065   if (!$This->HasProperty('Molecule')) {
4066     return undef;
4067   }
4068   my($Molecule);
4069   $Molecule = $This->GetProperty('Molecule');
4070 
4071   return $Molecule->_GetAtomRingsWithSizeLessThan($This, $RingSize);
4072 }
4073 
4074 # Get rings with size greater than specfied size as an array of references to arrays containing ring atoms...
4075 #
4076 sub GetRingsWithSizeGreaterThan {
4077   my($This, $RingSize) = @_;
4078 
4079   # Is this atom in a molecule?
4080   if (!$This->HasProperty('Molecule')) {
4081     return undef;
4082   }
4083   my($Molecule);
4084   $Molecule = $This->GetProperty('Molecule');
4085 
4086   return $Molecule->_GetAtomRingsWithSizeGreaterThan($This, $RingSize);
4087 }
4088 
4089 # Get next object ID...
4090 sub _GetNewObjectID {
4091   $ObjectID++;
4092   return $ObjectID;
4093 }
4094 
4095 # Return a string containing vertices, edges and other properties...
4096 sub StringifyAtom {
4097   my($This) = @_;
4098   my($AtomString, $ID, $Name, $AtomSymbol, $AtomicNumber, $XYZVector, $AtomicWeight, $ExactMass, $NumOfNeighbors, $NumOfBonds, $Valence, $MissingHydrogens, $TotalHydrogens, $ImplicitHydrogens, $ExplicitHydrogens, $FormalCharge, $Charge, $SpinMultiplicity, $FreeRadicalElectrons, $StereoCenter, $StereoCenterStatus, $StereoChemistry, $StereochemistryString, $RingAtom, $NumOfRings, $AromaticAtom);
4099 
4100   $ID = $This->GetID();
4101   $Name = $This->GetName();
4102   $AtomSymbol = $This->GetAtomSymbol();
4103   $AtomicNumber = $This->GetAtomicNumber();
4104   $XYZVector = $This->GetXYZVector();
4105 
4106   $AtomicWeight = $This->GetAtomicWeight();
4107   if (!defined $AtomicWeight) {
4108     $AtomicWeight = 'undefined';
4109   }
4110   $ExactMass = $This->GetExactMass();
4111   if (!defined $ExactMass) {
4112     $ExactMass = 'undefined';
4113   }
4114   $NumOfNeighbors = $This->GetNumOfNeighbors();
4115   if (!defined $NumOfNeighbors) {
4116     $NumOfNeighbors = 'undefined';
4117   }
4118   $NumOfBonds = $This->GetNumOfBonds();
4119   if (!defined $NumOfBonds) {
4120     $NumOfBonds = 'undefined';
4121   }
4122   $Valence = $This->GetValence();
4123   if (!defined $Valence) {
4124     $Valence = 'undefined';
4125   }
4126 
4127   $MissingHydrogens = $This->GetNumOfMissingHydrogens();
4128   if (!defined $MissingHydrogens) {
4129     $MissingHydrogens = 'undefined';
4130   }
4131   $TotalHydrogens = $This->GetNumOfHydrogens();
4132   if (!defined $TotalHydrogens) {
4133     $TotalHydrogens = 'undefined';
4134   }
4135   $ImplicitHydrogens = $This->GetNumOfImplicitHydrogens();
4136   if (!defined $ImplicitHydrogens) {
4137     $ImplicitHydrogens = 'undefined';
4138   }
4139   $ExplicitHydrogens = $This->GetNumOfExplicitHydrogens();
4140   if (!defined $ExplicitHydrogens) {
4141     $ExplicitHydrogens = 'undefined';
4142   }
4143 
4144   $FormalCharge = $This->GetFormalCharge();
4145   if (!defined $FormalCharge) {
4146     $FormalCharge = 'undefined';
4147   }
4148   $Charge = $This->GetCharge();
4149   if (!defined $Charge) {
4150     $Charge = 'undefined';
4151   }
4152 
4153   $SpinMultiplicity = $This->GetSpinMultiplicity();
4154   if (!defined $SpinMultiplicity) {
4155     $SpinMultiplicity = 'undefined';
4156   }
4157   $FreeRadicalElectrons = $This->GetFreeRadicalElectrons();
4158   if (!defined $FreeRadicalElectrons) {
4159     $FreeRadicalElectrons = 'undefined';
4160   }
4161 
4162   $RingAtom = $This->IsInRing();
4163   if (defined $RingAtom) {
4164     $RingAtom = $RingAtom  ? 'Yes' : 'No';
4165     $NumOfRings = $This->GetNumOfRings();
4166   }
4167   else {
4168     $RingAtom = 'undefined';
4169     $NumOfRings = 'undefined';
4170   }
4171 
4172   $AromaticAtom = $This->GetAromatic();
4173   if (defined $AromaticAtom) {
4174     $AromaticAtom = $AromaticAtom  ? 'Yes' : 'No';
4175   }
4176   else {
4177     $AromaticAtom = 'undefined';
4178   }
4179 
4180   $StereochemistryString = '';
4181   $StereoCenter = $This->GetStereoCenter();
4182   if (defined $StereoCenter) {
4183     $StereoCenterStatus = $This->IsStereoCenter() ? 'Yes' : 'No';
4184     $StereoChemistry = $This->GetStereochemistry();
4185     if (!defined $StereoChemistry) {
4186       $StereoChemistry = 'undefined';
4187     }
4188     $StereochemistryString = "StereoCenter: $StereoCenterStatus; Stereochemistry: $StereoChemistry";
4189   }
4190 
4191   $AtomString = "Atom: ID: $ID; Name: \"$Name\"; AtomSymbol: \"$AtomSymbol\"; AtomicNumber: $AtomicNumber; XYZ: $XYZVector; AtomicWeight: $AtomicWeight; ExactMass: $ExactMass; NumOfNeighbors: $NumOfNeighbors;  NumOfBonds: $NumOfBonds; Valence: $Valence; MissingHydrogens: $MissingHydrogens; TotalHydrogens: $TotalHydrogens; ImplicitHydrogens: $ImplicitHydrogens; ExplicitHydrogens: $ExplicitHydrogens; FormalCharge: $FormalCharge; Charge: $Charge; SpinMultiplicity: $SpinMultiplicity; FreeRadicalElectrons: $FreeRadicalElectrons; RingAtom: $RingAtom; NumOfAtomRings: $NumOfRings; AromaticAtom: $AromaticAtom";
4192 
4193   if ($StereochemistryString) {
4194     $AtomString .= "; $StereochemistryString";
4195   }
4196 
4197   return $AtomString;
4198 }
4199 
4200 # Load appropriate atom data files from <MayaChemTools>/lib directory used by various
4201 # object methods in the current class...
4202 #
4203 sub _LoadAtomClassData {
4204   my($MayaChemToolsLibDir);
4205 
4206   $MayaChemToolsLibDir = GetMayaChemToolsLibDirName();
4207 
4208   _LoadAtomValenceModelData($MayaChemToolsLibDir);
4209 
4210 }
4211 
4212 #
4213 # Load data for supported valence models...
4214 #
4215 sub _LoadAtomValenceModelData {
4216   my($MayaChemToolsLibDir) = @_;
4217   my($MDLValenceModelDataFile, $DaylightValenceModelDataFile);
4218 
4219   %MDLValenceModelDataMap = ();
4220   %DaylightValenceModelDataMap = ();
4221 
4222   $MDLValenceModelDataFile = $MayaChemToolsLibDir . "/data/MDLValenceModelData.csv";
4223   $DaylightValenceModelDataFile = $MayaChemToolsLibDir . "/data/DaylightValenceModelData.csv";
4224 
4225   if (! -e "$MDLValenceModelDataFile") {
4226     croak "Error: ${ClassName}::_LoadAtomValenceModelData: MayaChemTools package file, $MDLValenceModelDataFile, is missing: Possible installation problems...";
4227   }
4228 
4229   if (! -e "$DaylightValenceModelDataFile") {
4230     croak "Error: ${ClassName}::_LoadAtomValenceModelData: MayaChemTools package file, $DaylightValenceModelDataFile, is missing: Possible installation problems...";
4231   }
4232 
4233   _LoadValenceModelDataFile($MDLValenceModelDataFile, \%MDLValenceModelDataMap);
4234   _LoadValenceModelDataFile($DaylightValenceModelDataFile, \%DaylightValenceModelDataMap);
4235 
4236 }
4237 
4238 #
4239 # Load valence model data file...
4240 #
4241 sub _LoadValenceModelDataFile {
4242   my($DataFile, $DataMapRef) = @_;
4243 
4244   # File format:
4245   #
4246   # "AtomicNumber","ElementSymbol","FormalCharge","CommomValences"
4247   #
4248   my($InDelim, $Line, $NumOfCols, @ColLabels, @LineWords);
4249 
4250   $InDelim = "\,";
4251   open DATAFILE, "$DataFile" or croak "Couldn't open $DataFile: $! ...";
4252 
4253   # Skip lines up to column labels...
4254   LINE: while ($Line = GetTextLine(\*DATAFILE)) {
4255     if ($Line !~ /^#/) {
4256       last LINE;
4257     }
4258   }
4259   @ColLabels= quotewords($InDelim, 0, $Line);
4260   $NumOfCols = @ColLabels;
4261 
4262   my($AtomicNumber, $ElementSymbol, $FormalCharge, $CommonValences);
4263 
4264   # Process element data...
4265   LINE: while ($Line = GetTextLine(\*DATAFILE)) {
4266     if ($Line =~ /^#/) {
4267       next LINE;
4268     }
4269     @LineWords = ();
4270     @LineWords = quotewords($InDelim, 0, $Line);
4271     if (@LineWords != $NumOfCols) {
4272       croak "Error: ${ClassName}::_LoadValenceModelDataFile: The number of data fields, @LineWords, in $DataFile must be $NumOfCols.\nLine: $Line...";
4273     }
4274 
4275     ($AtomicNumber, $ElementSymbol, $FormalCharge, $CommonValences) = @LineWords;
4276 
4277     if (exists $DataMapRef->{$AtomicNumber}) {
4278       # Additional data for an element...
4279       if (exists $DataMapRef->{$AtomicNumber}{$FormalCharge}) {
4280         # Duplicate data entries for an element...
4281         carp "Warning: ${ClassName}::_LoadValenceModelDataFile: Ignoring valence data for element with atomic number $AtomicNumber and formal charge $FormalCharge in data file $DataFile: It has already been loaded.\nLine: $Line...";
4282         next LINE;
4283       }
4284     }
4285     else {
4286       # Data for a new element...
4287       %{$DataMapRef->{$AtomicNumber}} = ();
4288     }
4289 
4290     %{$DataMapRef->{$AtomicNumber}{$FormalCharge}} = ();
4291     $DataMapRef->{$AtomicNumber}{$FormalCharge}{ElementSymbol} = $ElementSymbol;
4292 
4293     @{$DataMapRef->{$AtomicNumber}{$FormalCharge}{CommonValences}} = ();
4294     @{$DataMapRef->{$AtomicNumber}{$FormalCharge}{CommonValences}} = sort { $a <=> $b } split /\,/, $CommonValences;
4295   }
4296   close DATAFILE;
4297 }
4298