       1 package MolecularDescriptors::RotatableBondsDescriptors;
       2 #
       3 # File: RotatableBondsDescriptors.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 TextUtil ();
      31 use Atom;
      32 use Molecule;
      33 use MolecularDescriptors::MolecularDescriptors;
      34
      35 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
      36
      37 @ISA = qw(MolecularDescriptors::MolecularDescriptors Exporter);
      38 @EXPORT = qw();
      39 @EXPORT_OK = qw(GetDescriptorNames);
      40
      41 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
      42
      43 # Setup class variables...
      44 my($ClassName, @DescriptorNames);
      45 _InitializeClass();
      46
      47 # Overload Perl functions...
      48 use overload '""' => 'StringifyRotatableBondsDescriptors';
      49
      50 # Class constructor...
      51 sub new {
      52   my($Class, %NamesAndValues) = @_;
      53
      54   # Initialize object...
      55   my $This = $Class->SUPER::new();
      56   bless $This, ref($Class) || $Class;
      57   $This->_InitializeRotatableBondsDescriptors();
      58
      59   $This->_InitializeRotatableBondsDescriptorsProperties(%NamesAndValues);
      60
      61   return $This;
      62 }
      63
      64 # Initialize class ...
      65 sub _InitializeClass {
      66   #Class name...
      67   $ClassName = __PACKAGE__;
      68
      69   # Descriptor names...
      70   @DescriptorNames = ('RotatableBonds');
      71
      72 }
      73
      74 # Get descriptor names as an array.
      75 #
      76 # This functionality can be either invoked as a class function or an
      77 # object method.
      78 #
      79 sub GetDescriptorNames {
      80   return @DescriptorNames;
      81 }
      82
      83 # Initialize object data...
      84 #
      85 sub _InitializeRotatableBondsDescriptors {
      86   my($This) = @_;
      87
      88   # Type of MolecularDescriptor...
      89   $This->{Type} = 'RotatableBonds';
      90
      91   # MayaChemTools rotatable bonds default definition corresponds to modifed
      92   # version of rotatable bonds definition used by Veber et al. [ Ref 92 ]
      93   #
      94   $This->{IgnoreTerminalBonds} = 1;
      95   $This->{IgnoreBondsToTripleBonds} = 1;
      96   $This->{IgnoreAmideBonds} = 1;
      97   $This->{IgnoreThioamideBonds} = 1;
      98   $This->{IgnoreSulfonamideBonds} = 1;
      99
     100   # Intialize descriptor names and values...
     101   $This->_InitializeDescriptorNamesAndValues(@DescriptorNames);
     102
     103   return $This;
     104 }
     105
     106 # Initialize object properties...
     107 #
     108 sub _InitializeRotatableBondsDescriptorsProperties {
     109   my($This, %NamesAndValues) = @_;
     110
     111   my($Name, $Value, $MethodName);
     112   while (($Name, $Value) = each  %NamesAndValues) {
     113     $MethodName = "Set${Name}";
     114     $This->$MethodName($Value);
     115   }
     116
     117   return $This;
     118 }
     119
     120 # Calculate number of rotatable bonds in a molecule...
     121 #
     122 # A rotatable bond is defined as any single bond which is not in a ring
     123 # and involves only non-hydrogen atoms. By default, the following types
     124 # of single bonds are not considered rotatable bonds:
     125 #
     126 #   . Terminal bonds
     127 #   . Bonds attached to triple bonds
     128 #   . Amide C-N bonds
     129 #   . Thioamide C-N bond bonds
     130 #   . Sulfonamide S-N bonds
     131 #
     132 # MayaChemTools rotatable bonds default definition corresponds to modifed
     133 # version of rotatable bonds definition used by Veber et al. [ Ref 92 ]
     134 #
     135 sub GenerateDescriptors {
     136   my($This) = @_;
     137
     138   # Initialize descriptor values...
     139   $This->_InitializeDescriptorValues();
     140
     141   # Check availability of molecule...
     142   if (!$This->{Molecule}) {
     143     carp "Warning: ${ClassName}->GenerateDescriptors: $This->{Type} molecular descriptors generation didn't succeed: Molecule data is not available: Molecule object hasn't been set...";
     144     return undef;
     145   }
     146
     147   # Calculate descriptor values...
     148   if (!$This->_CalculateDescriptorValues()) {
     149     carp "Warning: ${ClassName}->GenerateDescriptors: $This->{Type} molecular descriptors generation didn't succeed: Couldn't calculate RotatableBonds values...";
     150     return undef;
     151   }
     152
     153   # Set final descriptor values...
     154   $This->_SetFinalDescriptorValues();
     155
     156   return $This;
     157 }
     158
     159 # Calculate RotatableBonds value...
     160 #
     161 sub _CalculateDescriptorValues {
     162   my($This) = @_;
     163   my($Bond, $RotatableBonds, $Atom1, $Atom2);
     164
     165   $RotatableBonds = 0;
     166
     167   BOND: for $Bond ($This->{Molecule}->GetBonds()) {
     168     # Is it a non-ring ring bond?
     169     if (!$Bond->IsSingle() || $Bond->IsInRing()) {
     170       next BOND;
     171     }
     172
     173     ($Atom1, $Atom2) = $Bond->GetAtoms();
     174
     175     # Does bond contain any Hydrogen atoms?
     176     if ($Atom1->IsHydrogen() || $Atom2->IsHydrogen()) {
     177       next BOND;
     178     }
     179
     180     # Check for terminal bonds...
     181     if ($This->{IgnoreTerminalBonds} && $This->_IsTerminalBond($Atom1, $Atom2)) {
     182       next BOND;
     183     }
     184
     185     # Check for bonds attached to triple bonds...
     186     if ($This->{IgnoreBondsToTripleBonds} && $This->_IsAttachedToTripleBond($Atom1, $Atom2)) {
     187       next BOND;
     188     }
     189
     190     # Check for amide bonds...
     191     if ($This->{IgnoreAmideBonds} && $This->_IsAmideBond($Atom1, $Atom2)) {
     192       next BOND;
     193     }
     194
     195     # Check for amide bonds...
     196     if ($This->{IgnoreThioamideBonds} && $This->_IsThioamideBond($Atom1, $Atom2)) {
     197       next BOND;
     198     }
     199
     200     # Check for sulfonamide bonds...
     201     if ($This->{IgnoreSulfonamideBonds} && $This->_IsSulfonamideBond($Atom1, $Atom2)) {
     202       next BOND;
     203     }
     204
     205     $RotatableBonds += 1;
     206   }
     207
     208   # Track the calculated values...
     209   $This->{RotatableBonds} = $RotatableBonds;
     210
     211   return $This;
     212 }
     213
     214 # Is it a terminal bond?
     215 #
     216 sub _IsTerminalBond {
     217   my($This, $Atom1, $Atom2) = @_;
     218
     219   return ($Atom1->GetAtomicInvariantValue('X') <= 1 || $Atom2->GetAtomicInvariantValue('X') <= 1 ) ? 1 : 0;
     220 }
     221
     222 # Is it attached to a terminal bond?
     223 #
     224 sub _IsAttachedToTripleBond {
     225   my($This, $Atom1, $Atom2) = @_;
     226
     227   return ($Atom1->GetAtomicInvariantValue('LBO') == 3 || $Atom2->GetAtomicInvariantValue('LBO') == 3) ? 1 : 0;
     228 }
     229
     230 # Is it an amide bond?
     231 #
     232 # Amide: R-C(=O)-N(-R)(-R")
     233 #
     234 sub _IsAmideBond {
     235   my($This, $Atom1, $Atom2) = @_;
     236   my($CarbonAtom, $NitrogenAtom);
     237
     238   ($CarbonAtom, $NitrogenAtom) = (undef, undef);
     239
     240   if ($Atom1->IsCarbon() && $Atom2->IsNitrogen()) {
     241     ($CarbonAtom, $NitrogenAtom) = ($Atom1, $Atom2);
     242   }
     243   elsif ($Atom2->IsCarbon() && $Atom1->IsNitrogen()) {
     244     ($CarbonAtom, $NitrogenAtom) = ($Atom2, $Atom1);
     245   }
     246
     247   if (!$CarbonAtom) {
     248     return 0;
     249   }
     250
     251   return $CarbonAtom->DoesAtomNeighborhoodMatch('C.T3.DB1', ['O', 'N', 'C,H'], ['=', '-', '-']) ? 1 : 0;
     252 }
     253
     254 # Is it a thioamide bond?
     255 #
     256 # Thioamide: R-C(=S)-N(-R)(-R")
     257 #
     258 sub _IsThioamideBond {
     259   my($This, $Atom1, $Atom2) = @_;
     260   my($CarbonAtom, $NitrogenAtom);
     261
     262   ($CarbonAtom, $NitrogenAtom) = (undef, undef);
     263
     264   if ($Atom1->IsCarbon() && $Atom2->IsNitrogen()) {
     265     ($CarbonAtom, $NitrogenAtom) = ($Atom1, $Atom2);
     266   }
     267   elsif ($Atom2->IsCarbon() && $Atom1->IsNitrogen()) {
     268     ($CarbonAtom, $NitrogenAtom) = ($Atom2, $Atom1);
     269   }
     270
     271   if (!$CarbonAtom) {
     272     return 0;
     273   }
     274
     275   return $CarbonAtom->DoesAtomNeighborhoodMatch('C.T3.DB1', ['S', 'N', 'C,H'], ['=', '-', '-']) ? 1 : 0;
     276 }
     277
     278 # Is it a sulfonamide bond?
     279 #
     280 # Sulfonamide: R-S(=O)(=O)-N(-R)(-R")
     281 #
     282 sub _IsSulfonamideBond {
     283   my($This, $Atom1, $Atom2) = @_;
     284   my($SulfurAtom, $NitrogenAtom);
     285
     286   ($SulfurAtom, $NitrogenAtom) = (undef, undef);
     287
     288   if ($Atom1->IsSulfur() && $Atom2->IsNitrogen()) {
     289     ($SulfurAtom, $NitrogenAtom) = ($Atom1, $Atom2);
     290   }
     291   elsif ($Atom2->IsSulfur() && $Atom1->IsNitrogen()) {
     292     ($SulfurAtom, $NitrogenAtom) = ($Atom2, $Atom1);
     293   }
     294
     295   if (!$SulfurAtom) {
     296     return 0;
     297   }
     298
     299   return $SulfurAtom->DoesAtomNeighborhoodMatch('S.T4.DB2', ['O', 'O', 'N', '!O'], ['=', '=', '-', '-']) ? 1 : 0;
     300 }
     301
     302 # Setup final descriptor values...
     303 #
     304 sub _SetFinalDescriptorValues {
     305   my($This) = @_;
     306
     307   $This->{DescriptorsGenerated} = 1;
     308
     309   $This->SetDescriptorValues($This->{RotatableBonds});
     310
     311   return $This;
     312 }
     313
     314 # Return a string containg data for RotatableBondsDescriptors object...
     315 #
     316 sub StringifyRotatableBondsDescriptors {
     317   my($This) = @_;
     318   my($RotatableBondsDescriptorsString);
     319
     320   # Type of MolecularDescriptors...
     321   $RotatableBondsDescriptorsString = "MolecularDescriptorType: $This->{Type}; IgnoreTerminalBonds: " . ($This->{IgnoreTerminalBonds} ? "Yes" : "No") . "; IgnoreBondsToTripleBonds: " .  ($This->{IgnoreBondsToTripleBonds} ? "Yes" : "No") . "; IgnoreAmideBonds: " .  ($This->{IgnoreAmideBonds} ? "Yes" : "No") . "; IgnoreThioamideBonds: " .  ($This->{IgnoreThioamideBonds} ? "Yes" : "No") . "; IgnoreSulfonamideBonds: " .  ($This->{IgnoreSulfonamideBonds} ? "Yes" : "No");
     322
     323   # Setup molecular descriptor information...
     324   $RotatableBondsDescriptorsString .= "; " . $This->_StringifyDescriptorNamesAndValues();
     325
     326   return $RotatableBondsDescriptorsString;
     327 }
     328
     329 # Is it a RotatableBondsDescriptors object?
     330 sub _IsRotatableBondsDescriptors {
     331   my($Object) = @_;
     332
     333   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
     334 }
     335