       1 package AtomTypes::FunctionalClassAtomTypes;
       2 #
       3 # File: FunctionalClassAtomTypes.pm
       4 # Author: Manish Sud <msud@san.rr.com>
       5 #
       6 # Copyright (C) 2018 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