MayaChemTools

   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