MayaChemTools

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