       1 package AtomTypes::DREIDINGAtomTypes;
       2 #
       3 # File: DREIDINGAtomTypes.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(GetDREIDINGAtomTypesData GetAllPossibleDREIDINGAtomTypes GetAllPossibleDREIDINGNonHydrogenAtomTypes);
      37 @EXPORT_OK = qw();
      38
      39 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
      40
      41 # Setup class variables...
      42 my($ClassName, %DREIDINGAtomTypesDataMap);
      43 _InitializeClass();
      44
      45 # Overload Perl functions...
      46 use overload '""' => 'StringifyDREIDINGAtomTypes';
      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->_InitializeDREIDINGAtomTypes();
      56
      57   $This->_InitializeDREIDINGAtomTypesProperties(%NamesAndValues);
      58
      59   return $This;
      60 }
      61
      62 # Initialize class ...
      63 sub _InitializeClass {
      64   #Class name...
      65   $ClassName = __PACKAGE__;
      66
      67   # Initialize the data hash. It'll be loaded on demand later...
      68   %DREIDINGAtomTypesDataMap = ();
      69 }
      70
      71
      72 # Initialize object data...
      73 #
      74 sub _InitializeDREIDINGAtomTypes {
      75   my($This) = @_;
      76
      77   # Type of AtomTypes...
      78   $This->{Type} = 'DREIDING';
      79
      80   # By default, DREIDING atom types are also assigned to hydrogens...
      81   $This->{IgnoreHydrogens} = 0;
      82
      83   return $This;
      84 }
      85
      86 # Initialize object properties...
      87 #
      88 sub _InitializeDREIDINGAtomTypesProperties {
      89   my($This, %NamesAndValues) = @_;
      90
      91   my($Name, $Value, $MethodName);
      92   while (($Name, $Value) = each  %NamesAndValues) {
      93     $MethodName = "Set${Name}";
      94     $This->$MethodName($Value);
      95   }
      96
      97   # Make sure molecule object was specified...
      98   if (!exists $NamesAndValues{Molecule}) {
      99     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
     100   }
     101
     102   return $This;
     103 }
     104
     105 # Get DREIDING atom types and associated data loaded from DREIDING data file as
     106 # a reference to hash with the following hash data format:
     107 #
     108 # @{$DREIDINGAtomTypesDataMap{AtomTypes}} - Array of all possible atom types for all atoms
     109 # @{$DREIDINGAtomTypesDataMap{NonHydrogenAtomTypes}} - Array of all possible atom types for non-hydrogen atoms
     110 # @{$DREIDINGAtomTypesDataMap->{ColLabels}} - Array of column labels
     111 # %{$DREIDINGAtomTypesDataMap->{DataCol<Num>}} - Hash keys pair: <DataCol<Num>, AtomType>
     112 #
     113 # This functionality can be either invoked as a class function or an
     114 # object method.
     115 #
     116 sub GetDREIDINGAtomTypesData {
     117
     118   # Make sure data is loaded...
     119   _CheckAndLoadDREIDINGAtomTypesData();
     120
     121   return \%DREIDINGAtomTypesDataMap;
     122 }
     123
     124 # Get all possible DREIDING atom types corresponding to hydrogen and non-hydrogen
     125 # atoms as an array reference...
     126 #
     127 # This functionality can be either invoked as a class function or an
     128 # object method.
     129 #
     130 sub GetAllPossibleDREIDINGAtomTypes {
     131   return _GetAllPossibleDREIDINGAtomTypes();
     132 }
     133
     134 # Get all possible DREIDING atom types corresponding to non-hydrogen atoms
     135 # as an array reference...
     136 #
     137 # This functionality can be either invoked as a class function or an
     138 # object method.
     139 #
     140 sub GetAllPossibleDREIDINGNonHydrogenAtomTypes {
     141   my($NonHydrogensOnly);
     142
     143   $NonHydrogensOnly = 1;
     144   return _GetAllPossibleDREIDINGAtomTypes($NonHydrogensOnly);
     145 }
     146
     147 # Get all possible DREIDING atom types as an array reference...
     148 #
     149 sub _GetAllPossibleDREIDINGAtomTypes {
     150   my($NonHydrogensOnly) = @_;
     151   my($DREIDINGAtomTypesDataRef);
     152
     153   $NonHydrogensOnly = defined $NonHydrogensOnly ? $NonHydrogensOnly : 0;
     154
     155   $DREIDINGAtomTypesDataRef = GetDREIDINGAtomTypesData();
     156
     157   return $NonHydrogensOnly ? \@{$DREIDINGAtomTypesDataRef->{NonHydrogenAtomTypes}}: \@{$DREIDINGAtomTypesDataRef->{AtomTypes}};
     158 }
     159
     160 # Assign DREIDING [ Ref 88 ] atom types to all atoms...
     161 #
     162 # Notes:
     163 #     o 37 DREIDING atom types are listed
     164 #     o AtomTypes::DREIDINGAtomTypes.pm module is used to assign DREIDING atom types
     165 #     o Units:
     166 #         o ValenceBondRadius and NonBondRadius: Angstroms
     167 #         o ValenceAngle: Degrees
     168 #     o Five-character mnemonic label for DREIDING atom types
     169 #         o First two characters correspond to chemical symbol with an underscore as second
     170 #           character for elements with one character symbol
     171 #         o Third character describes hybridization: 1 - linear (sp); 2 - trigonal (sp2);
     172 #           3 = tetrahedral (sp3); R - sp2 involved in resonance situation
     173 #         o Fourth character used to indicate number of implicit hydrogens
     174 #         o Fourth and fifth chracters are used as indicators of alternate parameters: formal oxidation
     175 #           state, bridging hydrogens and so on. The _HB type denotes a hydrogen atom capable
     176 #           of forming hdyrogen bonds attached to (N, O, F). The H_b is the bridging hydrogen
     177 #           of diborane.
     178 #
     179 #
     180 sub AssignAtomTypes {
     181   my($This) = @_;
     182   my($Atom, $AtomType);
     183
     184   ATOM: for $Atom ($This->GetMolecule()->GetAtoms()) {
     185     if ($This->{IgnoreHydrogens} && $Atom->IsHydrogen()) {
     186       next ATOM;
     187     }
     188     $AtomType = $This->_GetAtomType($Atom);
     189     $This->SetAtomType($Atom, $AtomType);
     190   }
     191   return $This;
     192 }
     193
     194 # Get DREIDING atom type for atom...
     195 #
     196 sub _GetAtomType {
     197   my($This, $Atom) = @_;
     198   my($AtomType);
     199
     200   $AtomType = '';
     201
     202   ATOM: {
     203     if ($Atom->IsCarbon()) {
     204       $AtomType = $This->_GetAtomTypeForCarbon($Atom);
     205       last ATOM;
     206     }
     207     if ($Atom->IsNitrogen()) {
     208       $AtomType = $This->_GetAtomTypeForNitrogen($Atom);
     209       last ATOM;
     210     }
     211     if ($Atom->IsOxygen()) {
     212       $AtomType = $This->_GetAtomTypeForOxygen($Atom);
     213       last ATOM;
     214     }
     215     if ($Atom->IsPhosphorus()) {
     216       $AtomType = $This->_GetAtomTypeForPhosphorus($Atom);
     217       last ATOM;
     218     }
     219     if ($Atom->IsSulfur()) {
     220       $AtomType = $This->_GetAtomTypeForSulfur($Atom);
     221       last ATOM;
     222     }
     223     if ($Atom->IsHydrogen()) {
     224       $AtomType = $This->_GetAtomTypeForHydrogen($Atom);
     225       last ATOM;
     226     }
     227     $AtomType = $This->_GetAtomTypeForOtherAtoms($Atom);
     228   }
     229
     230   return $AtomType;
     231 }
     232
     233 # Get DREIDING atom type for Carbon atom...
     234 #
     235 sub _GetAtomTypeForCarbon {
     236   my($This, $Atom) = @_;
     237   my($AtomType, $NumOfSigmaBonds, $NumOfPiBonds);
     238
     239   $AtomType = 'None';
     240
     241   ($NumOfSigmaBonds, $NumOfPiBonds) = ('0') x 2;
     242
     243   ($NumOfSigmaBonds, $NumOfPiBonds) = $Atom->GetNumOfSigmaAndPiBondsToNonHydrogenAtoms();
     244   $NumOfSigmaBonds += $Atom->GetAtomicInvariantValue('H');
     245
     246   ATOMTYPE: {
     247     if ($Atom->IsAromatic()) {
     248       $AtomType = 'C_R';
     249       last ATOMTYPE;
     250     }
     251
     252     # Only single bonds...
     253     if ($NumOfPiBonds == 0) {
     254       $AtomType = 'C_3';
     255       last ATOMTYPE;
     256     }
     257
     258     # One double bond...
     259     if ($NumOfPiBonds == 1) {
     260       $AtomType = 'C_2';
     261       last ATOMTYPE;
     262     }
     263
     264     # One triple bond or two double bonds...
     265     if ($NumOfPiBonds == 2) {
     266       $AtomType = 'C_1';
     267       last ATOMTYPE;
     268     }
     269
     270     $AtomType = 'None';
     271     carp "Warning: ${ClassName}->_GetAtomTypeForCarbon: DREIDING atom types for Carbon cann't be assigned...";
     272   }
     273
     274   return $AtomType;
     275 }
     276
     277 # Get DREIDING atom type for Nitrogen atom...
     278 #
     279 sub _GetAtomTypeForNitrogen {
     280   my($This, $Atom) = @_;
     281   my($AtomType, $NumOfSigmaBonds, $NumOfPiBonds);
     282
     283   $AtomType = 'None';
     284
     285   ($NumOfSigmaBonds, $NumOfPiBonds) = ('0') x 2;
     286
     287   ($NumOfSigmaBonds, $NumOfPiBonds) = $Atom->GetNumOfSigmaAndPiBondsToNonHydrogenAtoms();
     288   $NumOfSigmaBonds += $Atom->GetAtomicInvariantValue('H');
     289
     290   ATOMTYPE: {
     291     if ($Atom->IsAromatic()) {
     292       $AtomType = 'N_R';
     293       last ATOMTYPE;
     294     }
     295
     296     # Only single bonds...
     297     if ($NumOfPiBonds == 0) {
     298       $AtomType = 'N_3';
     299       last ATOMTYPE;
     300     }
     301
     302     # One double bond...
     303     if ($NumOfPiBonds == 1) {
     304       $AtomType = 'N_2';
     305       last ATOMTYPE;
     306     }
     307
     308     # One triple bond or two double bonds...
     309     if ($NumOfPiBonds == 2) {
     310       $AtomType = 'N_1';
     311       last ATOMTYPE;
     312     }
     313
     314     $AtomType = 'None';
     315     carp "Warning: ${ClassName}->_GetAtomTypeForNitrogen: DREIDING atom types for Nitrogen cann't be assigned...";
     316   }
     317
     318   return $AtomType;
     319 }
     320
     321 # Get DREIDING atom type for Oxygen atom...
     322 #
     323 sub _GetAtomTypeForOxygen {
     324   my($This, $Atom) = @_;
     325   my($AtomType, $NumOfSigmaBonds, $NumOfPiBonds);
     326
     327   $AtomType = 'None';
     328
     329   ($NumOfSigmaBonds, $NumOfPiBonds) = ('0') x 2;
     330
     331   ($NumOfSigmaBonds, $NumOfPiBonds) = $Atom->GetNumOfSigmaAndPiBondsToNonHydrogenAtoms();
     332   $NumOfSigmaBonds += $Atom->GetAtomicInvariantValue('H');
     333
     334   ATOMTYPE: {
     335     if ($Atom->IsAromatic()) {
     336       $AtomType = 'O_R';
     337       last ATOMTYPE;
     338     }
     339
     340     # Only single bonds...
     341     if ($NumOfPiBonds == 0) {
     342       $AtomType = 'O_3';
     343       last ATOMTYPE;
     344     }
     345
     346     # One double bond...
     347     if ($NumOfPiBonds == 1) {
     348       $AtomType = 'O_2';
     349       last ATOMTYPE;
     350     }
     351
     352     # One triple bond or two double bonds...
     353     if ($NumOfPiBonds == 2) {
     354       $AtomType = 'O_1';
     355       last ATOMTYPE;
     356     }
     357
     358     $AtomType = 'None';
     359     carp "Warning: ${ClassName}->_GetAtomTypeForOxygen: DREIDING atom types for Oxygen cann't be assigned...";
     360   }
     361
     362   return $AtomType;
     363 }
     364
     365 # Get DREIDING atom type for Phosphorus atom...
     366 #
     367 sub _GetAtomTypeForPhosphorus {
     368   my($This, $Atom) = @_;
     369   my($AtomType);
     370
     371   $AtomType = 'P_3';
     372
     373   return $AtomType;
     374 }
     375
     376 # Get DREIDING atom type for Sulfur atom...
     377 #
     378 sub _GetAtomTypeForSulfur {
     379   my($This, $Atom) = @_;
     380   my($AtomType);
     381
     382   $AtomType = 'S_3';
     383
     384   return $AtomType;
     385 }
     386
     387 # Get DREIDING atom type for Hydrogen atom...
     388 #
     389 sub _GetAtomTypeForHydrogen {
     390   my($This, $Atom) = @_;
     391   my($AtomType, $NumOfNeighbors, $NeighborAtom, @NonHydrogenAtomNeighbors);
     392
     393   @NonHydrogenAtomNeighbors = $Atom->GetNonHydrogenAtomNeighbors();
     394
     395   $NumOfNeighbors = scalar @NonHydrogenAtomNeighbors;
     396   $NeighborAtom = $NonHydrogenAtomNeighbors[0];
     397
     398   ATOMTYPE: {
     399     if ($NumOfNeighbors > 1) {
     400       # Bridging hydrogen as in B2H6
     401       $AtomType = 'H___b';
     402       last ATOMTYPE;
     403     }
     404
     405     if ($NeighborAtom->GetAtomicNumber() =~ /^(7|8|9)$/) {
     406       # Involved in hydrogen bonding due to its attachment to N, O, or F
     407       $AtomType = 'H__HB';
     408       last ATOMTYPE;
     409     }
     410     $AtomType = 'H_';
     411   }
     412
     413   return $AtomType;
     414 }
     415
     416 # Get DREIDING atom type for atoms other than Carbon, Nitrogen, Oxygen, Phosporus,
     417 # Sulfur and Hydrogen...
     418 #
     419 sub _GetAtomTypeForOtherAtoms {
     420   my($This, $Atom) = @_;
     421   my($AtomType, $AtomicNumber, $AtomSymbol);
     422
     423   $AtomType = 'None';
     424
     425   $AtomicNumber = $Atom->GetAtomicNumber();
     426   $AtomSymbol = $Atom->GetAtomSymbol();
     427
     428   ATOMICNUMBER: {
     429     if ($AtomicNumber =~ /^(9|17|35|53)$/i) {
     430       # F, Cl, Br, I
     431       $AtomType = length($AtomSymbol) == 1 ? "${AtomSymbol}_" : $AtomSymbol;
     432       last ATOMICNUMBER;
     433     }
     434
     435     if ($AtomicNumber =~ /^5$/i) {
     436       # B: B_2 and B_3
     437       $AtomType = (($Atom->GetNumOfNonHydrogenAtomNeighbors() + $Atom->GetAtomicInvariantValue('H')) == 4) ? "B_3" : "B_2";
     438       last ATOMICNUMBER;
     439     }
     440
     441     if ($AtomicNumber =~ /^(13|14|31|32|33|34|49|50|51|52)$/i) {
     442       # Al, Si, Ga, Ge, As, Se, In, Sn, Sb, Te
     443       $AtomType = "${AtomSymbol}3";
     444       last ATOMICNUMBER;
     445     }
     446
     447     if ($AtomicNumber =~ /^(11|20|26|30)$/i) {
     448       # Na, Ca, Fe, Zn
     449       $AtomType = $AtomSymbol;
     450       last ATOMICNUMBER;
     451     }
     452
     453     $AtomType = 'None';
     454     carp "Warning: ${ClassName}->_GetAtomTypeForOtherAtoms: DREIDING atom types for atom, $AtomSymbol, with atomic number, $AtomicNumber, cann't be assigned...";
     455   }
     456
     457   return $AtomType;
     458 }
     459
     460 # Return a string containg data for DREIDINGAtomTypes object...
     461 #
     462 sub StringifyDREIDINGAtomTypes {
     463   my($This) = @_;
     464   my($AtomTypesString);
     465
     466   # Type of AtomTypes...
     467   $AtomTypesString = "AtomTypes: $This->{Type}; IgnoreHydrogens: " . ($This->{IgnoreHydrogens} ? "Yes" : "No");
     468
     469   # Setup atom types information...
     470   my($AtomID, $AtomType, @AtomTypesInfo, %AssignedAtomTypes);
     471
     472   @AtomTypesInfo = ();
     473   %AssignedAtomTypes = $This->GetAtomTypes();
     474
     475   for $AtomID (sort { $a <=> $b } keys %AssignedAtomTypes) {
     476     $AtomType = $AssignedAtomTypes{$AtomID} ? $AssignedAtomTypes{$AtomID} : 'None';
     477     push @AtomTypesInfo, "$AtomID:$AtomType";
     478   }
     479   $AtomTypesString .= "; AtomIDs:AtomTypes: <" . TextUtil::JoinWords(\@AtomTypesInfo, ", ", 0) . ">";
     480
     481   return $AtomTypesString;
     482 }
     483
     484 # Is it a DREIDINGAtomTypes object?
     485 sub _IsDREIDINGAtomTypes {
     486   my($Object) = @_;
     487
     488   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
     489 }
     490
     491 # Check and load DREIDING atom types data...
     492 #
     493 sub _CheckAndLoadDREIDINGAtomTypesData {
     494
     495   # Is it already loaded?
     496   if (exists $DREIDINGAtomTypesDataMap{AtomTypes}) {
     497     return;
     498   }
     499
     500   _LoadDREIDINGAtomTypesData();
     501 }
     502
     503 # Load DREIDING atom types data from the file assuming first column to be atom type symbol..
     504 #
     505 # Format:
     506 #
     507 # "AtomType","ValenceBondRadius","ValenceAngle"
     508 # "H_","0.330","180.0"
     509 # "C_3","0.770","109.471"
     510 # "C_R","0.700","120.0"
     511 # "C_2","0.670","120.0"
     512 # "C_1","0.602","180.0"
     513 # "N_3","0.702","106.7"
     514 #
     515 sub _LoadDREIDINGAtomTypesData {
     516   my($AtomTypesDataFile, $MayaChemToolsLibDir);
     517
     518   $MayaChemToolsLibDir = FileUtil::GetMayaChemToolsLibDirName();
     519
     520   $AtomTypesDataFile =  "$MayaChemToolsLibDir" . "/data/DREIDINGAtomTypes.csv";
     521   if (! -e "$AtomTypesDataFile") {
     522     croak "Error: MayaChemTools package file, $AtomTypesDataFile, is missing: Possible installation problems...";
     523   }
     524
     525   %DREIDINGAtomTypesDataMap = ();
     526   AtomTypes::AtomTypes::LoadAtomTypesData($AtomTypesDataFile, \%DREIDINGAtomTypesDataMap);
     527 }
     528