MayaChemTools

   1 package AtomTypes::FunctionalClassAtomTypes;
   2 #
   3 # File: FunctionalClassAtomTypes.pm
   4 # Author: Manish Sud <msud@san.rr.com>
   5 #
   6 # Copyright (C) 2024 Manish Sud. All rights reserved.
   7 #
   8 # This file is part of MayaChemTools.
   9 #
  10 # MayaChemTools is free software; you can redistribute it and/or modify it under
  11 # the terms of the GNU Lesser General Public License as published by the Free
  12 # Software Foundation; either version 3 of the License, or (at your option) any
  13 # later version.
  14 #
  15 # MayaChemTools is distributed in the hope that it will be useful, but without
  16 # any warranty; without even the implied warranty of merchantability of fitness
  17 # for a particular purpose.  See the GNU Lesser General Public License for more
  18 # details.
  19 #
  20 # You should have received a copy of the GNU Lesser General Public License
  21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
  22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
  23 # Boston, MA, 02111-1307, USA.
  24 #
  25 
  26 use strict;
  27 use Carp;
  28 use Exporter;
  29 use Scalar::Util ();
  30 use AtomTypes::AtomTypes;
  31 use Molecule;
  32 
  33 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  34 
  35 @ISA = qw(AtomTypes::AtomTypes Exporter);
  36 @EXPORT = qw();
  37 @EXPORT_OK = qw(IsFunctionalClassAvailable GetAvailableFunctionalClasses GetFunctionalClassesOrder);
  38 
  39 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  40 
  41 # Setup class variables...
  42 my($ClassName, @FunctionalClassesOrder, %AvailableFunctionalClasses, %AvailableFunctionalClassesByDescription);
  43 _InitializeClass();
  44 
  45 # Overload Perl functions...
  46 use overload '""' => 'StringifyFunctionalClassAtomTypes';
  47 
  48 # Class constructor...
  49 sub new {
  50   my($Class, %NamesAndValues) = @_;
  51 
  52   # Initialize object...
  53   my $This = $Class->SUPER::new();
  54   bless $This, ref($Class) || $Class;
  55   $This->_InitializeFunctionalClassAtomTypes();
  56 
  57   $This->_InitializeFunctionalClassAtomTypesProperties(%NamesAndValues);
  58 
  59   return $This;
  60 }
  61 
  62 # Initialize class ...
  63 sub _InitializeClass {
  64   #Class name...
  65   $ClassName = __PACKAGE__;
  66 
  67   # Initialize class level functional classes information...
  68   _InitializeFunctionalClasses();
  69 }
  70 
  71 # Initialize class level functional class information which doesn't change during
  72 # instantiations of objects...
  73 #
  74 sub _InitializeFunctionalClasses {
  75   # Available functional classes for generating atom types...
  76   #
  77   %AvailableFunctionalClasses = ();
  78   %AvailableFunctionalClasses = ('HBD' => 'HydrogenBondDonor',
  79                                      'HBA' => 'HydrogenBondAcceptor',
  80                                      'PI' => 'PositivelyIonizable',
  81                                      'NI' => 'NegativelyIonizable',
  82                                      'Ar' => 'Aromatic',
  83                                      'Hal' => 'Halogen',
  84                                      'H' => 'Hydrophobic',
  85                                      'RA' => 'RingAtom',
  86                                      'CA' => 'ChainAtom');
  87 
  88   # Setup available functional classe description to abbreviation map...
  89   #
  90   my($Key, $Value, $Description, @Descriptions);
  91   %AvailableFunctionalClassesByDescription = ();
  92   while (($Key, $Value) = each %AvailableFunctionalClasses) {
  93     @Descriptions = ($Value =~ /|/) ? (split /\|/, $Value) : ($Value);
  94     for $Description (@Descriptions) {
  95       $AvailableFunctionalClassesByDescription{$Description} = $Key;
  96     }
  97   }
  98 
  99   # Functional classes order used for generating atom types...
 100   #
 101   @FunctionalClassesOrder = ();
 102   @FunctionalClassesOrder = sort keys %AvailableFunctionalClasses;
 103 }
 104 
 105 # Initialize object data...
 106 #
 107 sub _InitializeFunctionalClassAtomTypes {
 108   my($This) = @_;
 109 
 110   # Type of AtomTypes...
 111   $This->{Type} = 'FunctionalClass';
 112 
 113   # By default hydrogens are also assigned atom types...
 114   $This->{IgnoreHydrogens} = 0;
 115 
 116   # Initialize atom types information...
 117   $This->_InitializeAtomTypesInformation();
 118 
 119   return $This;
 120 }
 121 
 122 # Inialize functional class information used for generating atom types...
 123 #
 124 sub _InitializeAtomTypesInformation {
 125   my($This) = @_;
 126 
 127   # Default functional classes to use for generating atom types: HBD, HBA, PI, NI, Ar, Hal
 128   #
 129   %{$This->{FunctionalClassesToUse}} = ();
 130   %{$This->{FunctionalClassesToUse}} = ('HBD' => 1, 'HBA' => 1,
 131                                         'PI' => 1, 'NI' => 1,
 132                                         'Ar' => 1,
 133                                         'Hal' => 1,
 134                                         'H' => 0,
 135                                         'RA' => 0, 'CA' => 0);
 136   return $This;
 137 }
 138 
 139 # Initialize object properties...
 140 #
 141 sub _InitializeFunctionalClassAtomTypesProperties {
 142   my($This, %NamesAndValues) = @_;
 143 
 144   my($Name, $Value, $MethodName);
 145   while (($Name, $Value) = each  %NamesAndValues) {
 146     $MethodName = "Set${Name}";
 147     $This->$MethodName($Value);
 148   }
 149 
 150   # Make sure molecule object was specified...
 151   if (!exists $NamesAndValues{Molecule}) {
 152     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
 153   }
 154 
 155   return $This;
 156 }
 157 
 158 # Disable change of AvailableFunctionalClasses...
 159 #
 160 sub SetAvailableFunctionalClasses {
 161   my($This) = @_;
 162 
 163   carp "Warning: ${ClassName}->SetFunctionalClassesOrder: Available functional classes can't be changed...";
 164 
 165   return $This;
 166 }
 167 
 168 # Disable change of functional classes order used for generation of atom types...
 169 #
 170 sub SetFunctionalClassesOrder {
 171   my($This) = @_;
 172 
 173   carp "Warning: ${ClassName}->SetFunctionalClassesOrder: functional classes order can't be changed...";
 174 
 175   return $This;
 176 }
 177 
 178 # Set functional classes to use for atom types...
 179 #
 180 sub SetFunctionalClassesToUse {
 181   my($This, @Values) = @_;
 182   my($FirstValue, $TypeOfFirstValue, $FunctionalClass, $SpecifiedFunctionalClass, $FunctionalClassValue, @SpecifiedFunctionalClasses, %FunctionalClassesToUse);
 183 
 184   if (!@Values) {
 185     carp "Warning: ${ClassName}->SetFunctionalClassesToUse: No values specified...";
 186     return;
 187   }
 188 
 189   $FirstValue = $Values[0];
 190   $TypeOfFirstValue = ref $FirstValue;
 191   @SpecifiedFunctionalClasses = ();
 192 
 193   if ($TypeOfFirstValue =~ /^ARRAY/) {
 194     push @SpecifiedFunctionalClasses, @{$FirstValue};
 195   }
 196   else {
 197     push @SpecifiedFunctionalClasses, @Values;
 198   }
 199 
 200   # Make sure specified FunctionalClasses are valid...
 201   for $SpecifiedFunctionalClass (@SpecifiedFunctionalClasses) {
 202     if (exists $AvailableFunctionalClasses{$SpecifiedFunctionalClass}) {
 203       $FunctionalClass = $SpecifiedFunctionalClass;
 204     }
 205     elsif ($AvailableFunctionalClassesByDescription{$SpecifiedFunctionalClass}) {
 206       $FunctionalClass = $AvailableFunctionalClassesByDescription{$SpecifiedFunctionalClass};
 207     }
 208     else {
 209       croak "Error: ${ClassName}->SetFunctionalClassesToUse: Specified functional class, $SpecifiedFunctionalClass, is not supported...\n ";
 210     }
 211     $FunctionalClassesToUse{$FunctionalClass} = 1;
 212   }
 213 
 214   # Set functional classes...
 215   for $FunctionalClass (keys %{$This->{FunctionalClassesToUse}}) {
 216     $This->{FunctionalClassesToUse}{$FunctionalClass} = 0;
 217     if (exists $FunctionalClassesToUse{$FunctionalClass}) {
 218       $This->{FunctionalClassesToUse}{$FunctionalClass} = 1;
 219     }
 220   }
 221 
 222   return $This;
 223 }
 224 
 225 # Is it an available FunctionalClass?
 226 #
 227 sub IsFunctionalClassAvailable {
 228   my($FirstParameter, $SecondParameter) = @_;
 229   my($This, $FunctionalClass, $Status);
 230 
 231   if ((@_ == 2) && (_IsFunctionalClassAtomTypes($FirstParameter))) {
 232     ($This, $FunctionalClass) = ($FirstParameter, $SecondParameter);
 233   }
 234   else {
 235     $FunctionalClass = $FirstParameter;
 236   }
 237   $Status = exists($AvailableFunctionalClasses{$FunctionalClass}) || exists($AvailableFunctionalClassesByDescription{$FunctionalClass}) ? 1 : 0;
 238 
 239   return $Status;
 240 }
 241 
 242 # Get a hash containing available functional classes and their description
 243 # as key/value pairs.
 244 #
 245 sub GetAvailableFunctionalClasses {
 246   return %AvailableFunctionalClasses;
 247 }
 248 
 249 # Get an array containing order of functional classes used to generate atom types...
 250 #
 251 sub GetFunctionalClassesOrder {
 252   return @FunctionalClassesOrder;
 253 }
 254 
 255 # Assign functional class atom types to all atoms...
 256 #
 257 # Let:
 258 #   HBD: HydrogenBondDonor
 259 #   HBA: HydrogenBondAcceptor
 260 #   PI :  PositivelyIonizable
 261 #   NI : NegativelyIonizable
 262 #   Ar : Aromatic
 263 #   Hal : Halogen
 264 #   H : Hydrophobic
 265 #   RA : RingAtom
 266 #   CA : ChainAtom
 267 #
 268 # Then:
 269 #
 270 #   Function class atom type specification for an atom corresponds to:
 271 #
 272 #     Ar.CA.H.HBA.HBD.Hal.NI.PI.RA
 273 #
 274 #   Default functional classes used are: HBD, HBA, PI, NI, Ar, Hal
 275 #
 276 #   FunctionalAtomTypes are assigned using the following definitions [ Ref 60-61, Ref 65-66 ]:
 277 #
 278 #     HydrogenBondDonor: NH, NH2, OH
 279 #     HydrogenBondAcceptor: N[!H], O
 280 #     PositivelyIonizable: +, NH2
 281 #     NegativelyIonizable: -, C(=O)OH, S(=O)OH, P(=O)OH
 282 #
 283 # Notes:
 284 #   . Final functional class atom type shows only those functional
 285 #     classes to which an atom belongs; others are not shown.
 286 #   . A null string is assigned as final atom type to those atom which
 287 #     don't belong to any of the specified functional classes.
 288 #
 289 # Examples of functional class atom types:
 290 #
 291 # HBD.HBA - Hydrogen bond donor and acceptor
 292 # HBD.RA - Hydrogen bond donor in a ring
 293 #
 294 #
 295 sub AssignAtomTypes {
 296   my($This) = @_;
 297   my($Atom, $AtomType, $FunctionalClass, $FunctionalClassValue, @FunctionalClasses);
 298 
 299   ATOM: for $Atom ($This->GetMolecule()->GetAtoms()) {
 300     if ($This->{IgnoreHydrogens} && $Atom->IsHydrogen()) {
 301       next ATOM;
 302     }
 303     @FunctionalClasses = ();
 304 
 305     # Go over functional classes...
 306     ATOMICINVARIANT: for $FunctionalClass (@FunctionalClassesOrder) {
 307       if (!$This->{FunctionalClassesToUse}{$FunctionalClass}) {
 308         next ATOMICINVARIANT;
 309       }
 310       if ($Atom->IsFunctionalClassType($FunctionalClass)) {
 311         push @FunctionalClasses, $FunctionalClass;
 312       }
 313     }
 314     # Create and assign atom type to atom...
 315     $AtomType = (scalar @FunctionalClasses) ? TextUtil::JoinWords(\@FunctionalClasses, ".", 0) : "None";
 316     $This->SetAtomType($Atom, $AtomType);
 317   }
 318   return $This;
 319 }
 320 
 321 # Are all atoms types successfully assigned?
 322 #
 323 # Notes:
 324 #   . Base class method is overridden to always return 1: An appropriate value, functional
 325 #     class types delmited by dot or None, is always assigned to atoms.
 326 #
 327 sub IsAtomTypesAssignmentSuccessful {
 328   my($This) = @_;
 329 
 330   return 1;
 331 }
 332 
 333 # Return a string containg data for FunctionalClassAtomTypes object...
 334 #
 335 sub StringifyFunctionalClassAtomTypes {
 336   my($This) = @_;
 337   my($AtomTypesString);
 338 
 339   # Type of AtomTypes...
 340   $AtomTypesString = "AtomTypes: $This->{Type}; IgnoreHydrogens: " . ($This->{IgnoreHydrogens} ? "Yes" : "No");
 341 
 342   # AvailableFunctionalClasses and FunctionalClassesToUse...
 343   my($FunctionalClass, @FunctionalClasses, @FunctionalClassesToUse);
 344 
 345   @FunctionalClassesToUse = ();
 346   @FunctionalClasses = ();
 347   for $FunctionalClass (@FunctionalClassesOrder) {
 348     push @FunctionalClasses, "$FunctionalClass: $AvailableFunctionalClasses{$FunctionalClass}";
 349     if ($This->{FunctionalClassesToUse}{$FunctionalClass}) {
 350       push @FunctionalClassesToUse, $FunctionalClass;
 351     }
 352   }
 353   $AtomTypesString .= "; FunctionalClassesToUse: <" . TextUtil::JoinWords(\@FunctionalClassesToUse, ", ", 0) . ">";
 354   $AtomTypesString .= "; FunctionalClassesOrder: <" . TextUtil::JoinWords(\@FunctionalClassesOrder, ", ", 0) . ">";
 355   $AtomTypesString .= "; AvailableFunctionalClasses: <" . TextUtil::JoinWords(\@FunctionalClasses, ", ", 0) . ">";
 356 
 357   # Setup atom types information...
 358   my($AtomID, $AtomType, @AtomTypesInfo, %AssignedAtomTypes);
 359 
 360   @AtomTypesInfo = ();
 361   %AssignedAtomTypes = $This->GetAtomTypes();
 362 
 363   for $AtomID (sort { $a <=> $b } keys %AssignedAtomTypes) {
 364     $AtomType = $AssignedAtomTypes{$AtomID} ? $AssignedAtomTypes{$AtomID} : 'None';
 365     push @AtomTypesInfo, "$AtomID:$AtomType";
 366   }
 367   $AtomTypesString .= "; AtomIDs:AtomTypes: <" . TextUtil::JoinWords(\@AtomTypesInfo, ", ", 0) . ">";
 368 
 369   return $AtomTypesString;
 370 }
 371 
 372 # Is it a FunctionalClassAtomTypes object?
 373 sub _IsFunctionalClassAtomTypes {
 374   my($Object) = @_;
 375 
 376   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
 377 }
 378