MayaChemTools

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