       1 package AtomTypes::AtomicInvariantsAtomTypes;
       2 #
       3 # $RCSfile: AtomicInvariantsAtomTypes.pm,v $
       4 # $Date: 2015/02/28 20:48:03 $
       5 # $Revision: 1.21 $
       6 #
       7 # Author: Manish Sud <msud@san.rr.com>
       8 #
       9 # Copyright (C) 2015 Manish Sud. All rights reserved.
      10 #
      11 # This file is part of MayaChemTools.
      12 #
      13 # MayaChemTools is free software; you can redistribute it and/or modify it under
      14 # the terms of the GNU Lesser General Public License as published by the Free
      15 # Software Foundation; either version 3 of the License, or (at your option) any
      16 # later version.
      17 #
      18 # MayaChemTools is distributed in the hope that it will be useful, but without
      19 # any warranty; without even the implied warranty of merchantability of fitness
      20 # for a particular purpose.  See the GNU Lesser General Public License for more
      21 # details.
      22 #
      23 # You should have received a copy of the GNU Lesser General Public License
      24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
      25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
      26 # Boston, MA, 02111-1307, USA.
      27 #
      28
      29 use strict;
      30 use Carp;
      31 use Exporter;
      32 use Scalar::Util ();
      33 use AtomTypes::AtomTypes;
      34 use Molecule;
      35
      36 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
      37
      38 @ISA = qw(AtomTypes::AtomTypes Exporter);
      39 @EXPORT = qw();
      40 @EXPORT_OK = qw(IsAtomicInvariantAvailable GetAvailableAtomicInvariants);
      41
      42 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
      43
      44 # Setup class variables...
      45 my($ClassName, @AtomicInvariantsOrder, %AvailableAtomicInvariants, %AvailableAtomicInvariantsByDescription);
      46 _InitializeClass();
      47
      48 # Overload Perl functions...
      49 use overload '""' => 'StringifyAtomicInvariantsAtomTypes';
      50
      51 # Class constructor...
      52 sub new {
      53   my($Class, %NamesAndValues) = @_;
      54
      55   # Initialize object...
      56   my $This = $Class->SUPER::new();
      57   bless $This, ref($Class) || $Class;
      58   $This->_InitializeAtomicInvariantsAtomTypes();
      59
      60   $This->_InitializeAtomicInvariantsAtomTypesProperties(%NamesAndValues);
      61
      62   return $This;
      63 }
      64
      65 # Initialize class ...
      66 sub _InitializeClass {
      67   #Class name...
      68   $ClassName = __PACKAGE__;
      69
      70   # Initialize class atomic invariants...
      71   _InitializeClassAtomicInvariants();
      72 }
      73
      74 # Initialize class level atomic invariants information which doesn't change during
      75 # instantiations of objects...
      76 #
      77 sub _InitializeClassAtomicInvariants {
      78   # Available atomic invariants for generating atom types...
      79   #
      80   %AvailableAtomicInvariants = ();
      81   %AvailableAtomicInvariants = ('AS' => 'AtomSymbol|ElementSymbol',
      82                                 'X' => 'NumOfNonHydrogenAtomNeighbors|NumOfHeavyAtomNeighbors',
      83                                 'BO' => 'SumOfBondOrdersToNonHydrogenAtoms|SumOfBondOrdersToHeavyAtoms',
      84                                 'LBO' => 'LargestBondOrderToNonHydrogenAtoms|LargestBondOrderToHeavyAtoms',
      85                                 'SB' => 'NumOfSingleBondsToNonHydrogenAtoms|NumOfSingleBondsToHeavyAtoms',
      86                                 'DB' => 'NumOfDoubleBondsToNonHydrogenAtoms|NumOfDoubleBondsToHeavyAtoms',
      87                                 'TB' => 'NumOfTripleBondsToNonHydrogenAtoms|NumOfTripleBondsToHeavyAtoms',
      88                                 'H' => 'NumOfImplicitAndExplicitHydrogens',
      89                                 'Ar' => 'Aromatic',
      90                                 'RA' => 'RingAtom',
      91                                 'FC' => 'FormalCharge',
      92                                 'MN' => 'MassNumber',
      93                                 'SM' => 'SpinMultiplicity');
      94
      95   # Setup available atomic invariants description to abbreviation map...
      96   #
      97   my($Key, $Value, $Description, @Descriptions);
      98   %AvailableAtomicInvariantsByDescription = ();
      99   while (($Key, $Value) = each %AvailableAtomicInvariants) {
     100     @Descriptions = ($Value =~ /|/) ? (split /\|/, $Value) : ($Value);
     101     for $Description (@Descriptions) {
     102       $AvailableAtomicInvariantsByDescription{$Description} = $Key;
     103     }
     104   }
     105
     106   # Atomic invariants order used for generating atom types...
     107   #
     108   @AtomicInvariantsOrder = ();
     109   @AtomicInvariantsOrder = ('AS', 'X', 'BO', 'LBO', 'SB', 'DB', 'TB', 'H', 'Ar', 'RA', 'FC', 'MN', 'SM');
     110 }
     111
     112 # Initialize object data...
     113 #
     114 sub _InitializeAtomicInvariantsAtomTypes {
     115   my($This) = @_;
     116
     117   # Type of AtomTypes...
     118   $This->{Type} = 'AtomicInvariants';
     119
     120   # By default hydrogens are also assigned atom types...
     121   $This->{IgnoreHydrogens} = 0;
     122
     123   # Initialize atom types information...
     124   $This->_InitializeAtomTypesInformation();
     125
     126   return $This;
     127 }
     128
     129 # Inialize atomic invariants information used for generating atom types...
     130 #
     131 sub _InitializeAtomTypesInformation {
     132   my($This) = @_;
     133
     134   # Default atomic invariants to use for generating atom types: AS, X, BO, H, FC
     135   #
     136   %{$This->{AtomicInvariantsToUse}} = ();
     137   %{$This->{AtomicInvariantsToUse}} = ('AS' => 1, 'X' => 1, 'BO' => 1, 'LBO' => 0,
     138                                        'SB' => 0, 'DB' => 0, 'TB' => 0,
     139                                        'H' => 1, 'Ar' => 0, 'RA' => 0, 'FC' => 1, 'MN' => 0, 'SM' => 0);
     140
     141   return $This;
     142 }
     143
     144 # Initialize object properties...
     145 #
     146 sub _InitializeAtomicInvariantsAtomTypesProperties {
     147   my($This, %NamesAndValues) = @_;
     148
     149   my($Name, $Value, $MethodName);
     150   while (($Name, $Value) = each  %NamesAndValues) {
     151     $MethodName = "Set${Name}";
     152     $This->$MethodName($Value);
     153   }
     154
     155   # Make sure molecule object was specified...
     156   if (!exists $NamesAndValues{Molecule}) {
     157     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
     158   }
     159
     160   return $This;
     161 }
     162
     163 # Disable change of AvailableAtomicInvariants...
     164 #
     165 sub SetAvailableAtomicInvariants {
     166   my($This) = @_;
     167
     168   carp "Warning: ${ClassName}->SetAtomicInvariantsOrder: Available atomic invariants can't be changed...";
     169
     170   return $This;
     171 }
     172
     173 # Disable change of atomic invariants order used for generation of atom types...
     174 #
     175 sub SetAtomicInvariantsOrder {
     176   my($This) = @_;
     177
     178   carp "Warning: ${ClassName}->SetAtomicInvariantsOrder: Atomic invariants order can't be changed...";
     179
     180   return $This;
     181 }
     182
     183 # Set atom invariants to use for atom types...
     184 #
     185 sub SetAtomicInvariantsToUse {
     186   my($This, @Values) = @_;
     187   my($FirstValue, $TypeOfFirstValue, $AtomicInvariant, $SpecifiedAtomicInvariant, $AtomicInvariantValue, @SpecifiedAtomicInvariants, %AtomicInvariantsToUse);
     188
     189   if (!@Values) {
     190     carp "Warning: ${ClassName}->SetAtomicInvariantsToUse: No values specified...";
     191     return;
     192   }
     193
     194   $FirstValue = $Values[0];
     195   $TypeOfFirstValue = ref $FirstValue;
     196   @SpecifiedAtomicInvariants = ();
     197
     198   if ($TypeOfFirstValue =~ /^ARRAY/) {
     199     push @SpecifiedAtomicInvariants, @{$FirstValue};
     200   }
     201   else {
     202     push @SpecifiedAtomicInvariants, @Values;
     203   }
     204
     205   # Make sure specified AtomicInvariants are valid...
     206   for $SpecifiedAtomicInvariant (@SpecifiedAtomicInvariants) {
     207     if (exists $AvailableAtomicInvariants{$SpecifiedAtomicInvariant}) {
     208       $AtomicInvariant = $SpecifiedAtomicInvariant;
     209     }
     210     elsif ($AvailableAtomicInvariantsByDescription{$SpecifiedAtomicInvariant}) {
     211       $AtomicInvariant = $AvailableAtomicInvariantsByDescription{$SpecifiedAtomicInvariant};
     212     }
     213     else {
     214       croak "Error: ${ClassName}->SetAtomicInvariantsToUse: Specified atomic invariant, $SpecifiedAtomicInvariant, is not supported...\n ";
     215     }
     216     $AtomicInvariantsToUse{$AtomicInvariant} = 1;
     217   }
     218
     219   # Make sure AtomSymbol is always used...
     220   if (!(exists($AtomicInvariantsToUse{AS}) && $AtomicInvariantsToUse{AS} == 1)) {
     221     croak "Error: ${ClassName}->SetAtomicInvariantsToUse: AtomicInvariant AtomSymbol must be specified...\n ";
     222   }
     223
     224   # Set atomic invariants...
     225   for $AtomicInvariant (keys %{$This->{AtomicInvariantsToUse}}) {
     226     $This->{AtomicInvariantsToUse}{$AtomicInvariant} = 0;
     227     if (exists $AtomicInvariantsToUse{$AtomicInvariant}) {
     228       $This->{AtomicInvariantsToUse}{$AtomicInvariant} = 1;
     229     }
     230   }
     231
     232   return $This;
     233 }
     234
     235 # Is it an available AtomicInvariant?
     236 #
     237 sub IsAtomicInvariantAvailable {
     238   my($FirstParameter, $SecondParameter) = @_;
     239   my($This, $AtomicInvariant, $Status);
     240
     241   if ((@_ == 2) && (_IsAtomicInvariantsAtomTypes($FirstParameter))) {
     242     ($This, $AtomicInvariant) = ($FirstParameter, $SecondParameter);
     243   }
     244   else {
     245     $AtomicInvariant = $FirstParameter;
     246   }
     247   $Status = exists($AvailableAtomicInvariants{$AtomicInvariant}) || exists($AvailableAtomicInvariantsByDescription{$AtomicInvariant}) ? 1 : 0;
     248
     249   return $Status;
     250 }
     251
     252 # Get a hash containing available atomic invariants and their description
     253 # as key/value pairs.
     254 #
     255 sub GetAvailableAtomicInvariants {
     256   return %AvailableAtomicInvariants;
     257 }
     258
     259 # Get an array containing order of atomic invariants used to generate atom types...
     260 #
     261 sub GetAtomicInvariantsOrder {
     262   return @AtomicInvariantsOrder;
     263 }
     264
     265 # Assign atom types to all atoms...
     266 #
     267 # Let:
     268 #   AS = Atom symbol corresponding to element symbol
     269 #
     270 #   X<n>   = Number of non-hydrogen atom neighbors or heavy atoms attached to atom
     271 #   BO<n> = Sum of bond orders to non-hydrogen atom neighbors or heavy atoms attached to atom
     272 #   LBO<n> = Largest bond order of non-hydrogen atom neighbors or heavy atoms attached to atom
     273 #   SB<n> = Number of single bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
     274 #   DB<n> = Number of double bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
     275 #   TB<n> = Number of triple bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
     276 #   H<n>   = Number of implicit and explicit hydrogens for atom
     277 #   Ar     = Aromatic annotation indicating whether atom is aromatic
     278 #   RA     = Ring atom annotation indicating whether atom is a ring
     279 #   FC<+n/-n> = Formal charge assigned to atom
     280 #   MN<n> = Mass number indicating isotope other than most abundant isotope
     281 #   SM<n> = Spin multiplicity of atom. Possible values: 1 (singlet), 2 (doublet) or 3 (triplet)
     282 #
     283 # Then:
     284 #
     285 #   AtomType specification corresponds to:
     286 #
     287 #     AS.X<n>.BO<n>.LBO<n>.<SB><n>.<DB><n>.<TB><n>.H<n>.Ar.RA.FC<+n/-n>.MN<n>.SM<n>
     288 #
     289 # Except for AS which is a required atomic invariant in atom types, all other atomic invariants are
     290 # optional. Default atomic invariants used for AtomID are: AS, X<n>, BO<n>, H<n>, FC<+n/-n>.
     291 # AtomID specification doesn't include atomic invariants with zero or undefined values.
     292 #
     293 # Notes:
     294 #   . AtomicInvariants with zero or undefined values are not shown.
     295 #   . LBO with value of 1 is not shown. And absence of LBO in AtomTypes implies the largest
     296 #     bond order value is one.
     297 #   . SB, DB and TB with values of zero are not shown.
     298 #   . The difference in BO and X values corresponds to numbed of pi electrons [ Ref 57 ].
     299 #
     300 # Examples of atomic invariant atom types:
     301 #
     302 #   O.X1.BO1.H1 - Hydroxyl oxygen in carboxylate with attached hydrogen and no explicit charge
     303 #   O.X1.BO1.FC-1 - Hydroxyl ozygen in carboxylate with explicit negative charge
     304 #   O.X1.BO2 - Carbonyl oxygen in carboxylate with double bond to carbon
     305 #   O.X2.BO2 - Hydroxyl ozygen in carboxylate attached to carbonyl carbon and another heavy atom
     306 #
     307 #   C.X2.BO3.H1.Ar - Aromatic carbon
     308 #
     309 sub AssignAtomTypes {
     310   my($This) = @_;
     311   my($Atom, $AtomType, $AtomicInvariant, $AtomicInvariantValue, @AtomicInvariants);
     312
     313   ATOM: for $Atom ($This->GetMolecule()->GetAtoms()) {
     314     if ($This->{IgnoreHydrogens} && $Atom->IsHydrogen()) {
     315       next ATOM;
     316     }
     317     @AtomicInvariants = ();
     318
     319     # Go over atomic invariants...
     320     ATOMICINVARIANT: for $AtomicInvariant (@AtomicInvariantsOrder) {
     321       if (!$This->{AtomicInvariantsToUse}{$AtomicInvariant}) {
     322         next ATOMICINVARIANT;
     323       }
     324       $AtomicInvariantValue = $Atom->GetAtomicInvariantValue($AtomicInvariant);
     325       if (!(defined($AtomicInvariantValue) && $AtomicInvariantValue)) {
     326         next ATOMICINVARIANT;
     327       }
     328       if ($AtomicInvariant =~ /^AS$/i) {
     329         push @AtomicInvariants, $AtomicInvariantValue;
     330       }
     331       elsif ($AtomicInvariant =~ /^Ar$/i) {
     332         push @AtomicInvariants, "Ar";
     333       }
     334       elsif ($AtomicInvariant =~ /^RA$/i) {
     335         push @AtomicInvariants, "RA";
     336       }
     337       elsif ($AtomicInvariant =~ /^FC$/i) {
     338         push @AtomicInvariants, ($AtomicInvariantValue > 0) ? "FC+${AtomicInvariantValue}" : "FC${AtomicInvariantValue}";
     339       }
     340       elsif ($AtomicInvariant =~ /^LBO$/i) {
     341         if ($AtomicInvariantValue > 1) {
     342           push @AtomicInvariants, "${AtomicInvariant}${AtomicInvariantValue}";
     343         }
     344       }
     345       else {
     346         push @AtomicInvariants, "${AtomicInvariant}${AtomicInvariantValue}";
     347       }
     348     }
     349     # Create and assign atom type to atom...
     350     $AtomType = TextUtil::JoinWords(\@AtomicInvariants, ".", 0);
     351     $This->SetAtomType($Atom, $AtomType);
     352   }
     353   return $This;
     354 }
     355
     356 # Are all atoms types successfully assigned?
     357 #
     358 # Notes:
     359 #   . Base class method is overridden to always return 1: An appropriate value, atomic invariant
     360 #     atom types delimited by dot, is always assigned to atoms.
     361 #
     362 sub IsAtomTypesAssignmentSuccessful {
     363   my($This) = @_;
     364
     365   return 1;
     366 }
     367
     368 # Return a string containg data for AtomicInvariantsAtomTypes object...
     369 #
     370 sub StringifyAtomicInvariantsAtomTypes {
     371   my($This) = @_;
     372   my($AtomTypesString);
     373
     374   # Type of AtomTypes...
     375   $AtomTypesString = "AtomTypes: $This->{Type}; IgnoreHydrogens: " . ($This->{IgnoreHydrogens} ? "Yes" : "No");
     376
     377   # AvailableAtomicInvariants and AtomicInvariantsToUse...
     378   my($AtomicInvariant, @AtomicInvariants, @AtomicInvariantsToUse);
     379
     380   @AtomicInvariantsToUse = ();
     381   @AtomicInvariants = ();
     382   for $AtomicInvariant (@AtomicInvariantsOrder) {
     383     push @AtomicInvariants, "$AtomicInvariant: $AvailableAtomicInvariants{$AtomicInvariant}";
     384     if ($This->{AtomicInvariantsToUse}{$AtomicInvariant}) {
     385       push @AtomicInvariantsToUse, $AtomicInvariant;
     386     }
     387   }
     388   $AtomTypesString .= "; AtomicInvariantsToUse: <" . TextUtil::JoinWords(\@AtomicInvariantsToUse, ", ", 0) . ">";
     389   $AtomTypesString .= "; AtomicInvariantsOrder: <" . TextUtil::JoinWords(\@AtomicInvariantsOrder, ", ", 0) . ">";
     390   $AtomTypesString .= "; AvailableAtomicInvariants: <" . TextUtil::JoinWords(\@AtomicInvariants, ", ", 0) . ">";
     391
     392   # Setup atom types information...
     393   my($AtomID, $AtomType, @AtomTypesInfo, %AssignedAtomTypes);
     394
     395   @AtomTypesInfo = ();
     396   %AssignedAtomTypes = $This->GetAtomTypes();
     397
     398   for $AtomID (sort { $a <=> $b } keys %AssignedAtomTypes) {
     399     $AtomType = $AssignedAtomTypes{$AtomID} ? $AssignedAtomTypes{$AtomID} : 'None';
     400     push @AtomTypesInfo, "$AtomID:$AtomType";
     401   }
     402   $AtomTypesString .= "; AtomIDs:AtomTypes: <" . TextUtil::JoinWords(\@AtomTypesInfo, ", ", 0) . ">";
     403
     404   return $AtomTypesString;
     405 }
     406
     407 # Is it a AtomicInvariantsAtomTypes object?
     408 sub _IsAtomicInvariantsAtomTypes {
     409   my($Object) = @_;
     410
     411   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
     412 }
     413