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