1 package AtomTypes::AtomicInvariantsAtomTypes; 2 # 3 # $RCSfile: AtomicInvariantsAtomTypes.pm,v $ 4 # $Date: 2015/02/28 20:48:03 $ 5 # $Revision: 1.21 $ 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(IsAtomicInvariantAvailable GetAvailableAtomicInvariants); 41 42 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 43 44 # Setup class variables... 45 my($ClassName, @AtomicInvariantsOrder, %AvailableAtomicInvariants, %AvailableAtomicInvariantsByDescription); 46 _InitializeClass(); 47 48 # Overload Perl functions... 49 use overload '""' => 'StringifyAtomicInvariantsAtomTypes'; 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->_InitializeAtomicInvariantsAtomTypes(); 59 60 $This->_InitializeAtomicInvariantsAtomTypesProperties(%NamesAndValues); 61 62 return $This; 63 } 64 65 # Initialize class ... 66 sub _InitializeClass { 67 #Class name... 68 $ClassName = __PACKAGE__; 69 70 # Initialize class atomic invariants... 71 _InitializeClassAtomicInvariants(); 72 } 73 74 # Initialize class level atomic invariants information which doesn't change during 75 # instantiations of objects... 76 # 77 sub _InitializeClassAtomicInvariants { 78 # Available atomic invariants for generating atom types... 79 # 80 %AvailableAtomicInvariants = (); 81 %AvailableAtomicInvariants = ('AS' => 'AtomSymbol|ElementSymbol', 82 'X' => 'NumOfNonHydrogenAtomNeighbors|NumOfHeavyAtomNeighbors', 83 'BO' => 'SumOfBondOrdersToNonHydrogenAtoms|SumOfBondOrdersToHeavyAtoms', 84 'LBO' => 'LargestBondOrderToNonHydrogenAtoms|LargestBondOrderToHeavyAtoms', 85 'SB' => 'NumOfSingleBondsToNonHydrogenAtoms|NumOfSingleBondsToHeavyAtoms', 86 'DB' => 'NumOfDoubleBondsToNonHydrogenAtoms|NumOfDoubleBondsToHeavyAtoms', 87 'TB' => 'NumOfTripleBondsToNonHydrogenAtoms|NumOfTripleBondsToHeavyAtoms', 88 'H' => 'NumOfImplicitAndExplicitHydrogens', 89 'Ar' => 'Aromatic', 90 'RA' => 'RingAtom', 91 'FC' => 'FormalCharge', 92 'MN' => 'MassNumber', 93 'SM' => 'SpinMultiplicity'); 94 95 # Setup available atomic invariants description to abbreviation map... 96 # 97 my($Key, $Value, $Description, @Descriptions); 98 %AvailableAtomicInvariantsByDescription = (); 99 while (($Key, $Value) = each %AvailableAtomicInvariants) { 100 @Descriptions = ($Value =~ /|/) ? (split /\|/, $Value) : ($Value); 101 for $Description (@Descriptions) { 102 $AvailableAtomicInvariantsByDescription{$Description} = $Key; 103 } 104 } 105 106 # Atomic invariants order used for generating atom types... 107 # 108 @AtomicInvariantsOrder = (); 109 @AtomicInvariantsOrder = ('AS', 'X', 'BO', 'LBO', 'SB', 'DB', 'TB', 'H', 'Ar', 'RA', 'FC', 'MN', 'SM'); 110 } 111 112 # Initialize object data... 113 # 114 sub _InitializeAtomicInvariantsAtomTypes { 115 my($This) = @_; 116 117 # Type of AtomTypes... 118 $This->{Type} = 'AtomicInvariants'; 119 120 # By default hydrogens are also assigned atom types... 121 $This->{IgnoreHydrogens} = 0; 122 123 # Initialize atom types information... 124 $This->_InitializeAtomTypesInformation(); 125 126 return $This; 127 } 128 129 # Inialize atomic invariants information used for generating atom types... 130 # 131 sub _InitializeAtomTypesInformation { 132 my($This) = @_; 133 134 # Default atomic invariants to use for generating atom types: AS, X, BO, H, FC 135 # 136 %{$This->{AtomicInvariantsToUse}} = (); 137 %{$This->{AtomicInvariantsToUse}} = ('AS' => 1, 'X' => 1, 'BO' => 1, 'LBO' => 0, 138 'SB' => 0, 'DB' => 0, 'TB' => 0, 139 'H' => 1, 'Ar' => 0, 'RA' => 0, 'FC' => 1, 'MN' => 0, 'SM' => 0); 140 141 return $This; 142 } 143 144 # Initialize object properties... 145 # 146 sub _InitializeAtomicInvariantsAtomTypesProperties { 147 my($This, %NamesAndValues) = @_; 148 149 my($Name, $Value, $MethodName); 150 while (($Name, $Value) = each %NamesAndValues) { 151 $MethodName = "Set${Name}"; 152 $This->$MethodName($Value); 153 } 154 155 # Make sure molecule object was specified... 156 if (!exists $NamesAndValues{Molecule}) { 157 croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule..."; 158 } 159 160 return $This; 161 } 162 163 # Disable change of AvailableAtomicInvariants... 164 # 165 sub SetAvailableAtomicInvariants { 166 my($This) = @_; 167 168 carp "Warning: ${ClassName}->SetAtomicInvariantsOrder: Available atomic invariants can't be changed..."; 169 170 return $This; 171 } 172 173 # Disable change of atomic invariants order used for generation of atom types... 174 # 175 sub SetAtomicInvariantsOrder { 176 my($This) = @_; 177 178 carp "Warning: ${ClassName}->SetAtomicInvariantsOrder: Atomic invariants order can't be changed..."; 179 180 return $This; 181 } 182 183 # Set atom invariants to use for atom types... 184 # 185 sub SetAtomicInvariantsToUse { 186 my($This, @Values) = @_; 187 my($FirstValue, $TypeOfFirstValue, $AtomicInvariant, $SpecifiedAtomicInvariant, $AtomicInvariantValue, @SpecifiedAtomicInvariants, %AtomicInvariantsToUse); 188 189 if (!@Values) { 190 carp "Warning: ${ClassName}->SetAtomicInvariantsToUse: No values specified..."; 191 return; 192 } 193 194 $FirstValue = $Values[0]; 195 $TypeOfFirstValue = ref $FirstValue; 196 @SpecifiedAtomicInvariants = (); 197 198 if ($TypeOfFirstValue =~ /^ARRAY/) { 199 push @SpecifiedAtomicInvariants, @{$FirstValue}; 200 } 201 else { 202 push @SpecifiedAtomicInvariants, @Values; 203 } 204 205 # Make sure specified AtomicInvariants are valid... 206 for $SpecifiedAtomicInvariant (@SpecifiedAtomicInvariants) { 207 if (exists $AvailableAtomicInvariants{$SpecifiedAtomicInvariant}) { 208 $AtomicInvariant = $SpecifiedAtomicInvariant; 209 } 210 elsif ($AvailableAtomicInvariantsByDescription{$SpecifiedAtomicInvariant}) { 211 $AtomicInvariant = $AvailableAtomicInvariantsByDescription{$SpecifiedAtomicInvariant}; 212 } 213 else { 214 croak "Error: ${ClassName}->SetAtomicInvariantsToUse: Specified atomic invariant, $SpecifiedAtomicInvariant, is not supported...\n "; 215 } 216 $AtomicInvariantsToUse{$AtomicInvariant} = 1; 217 } 218 219 # Make sure AtomSymbol is always used... 220 if (!(exists($AtomicInvariantsToUse{AS}) && $AtomicInvariantsToUse{AS} == 1)) { 221 croak "Error: ${ClassName}->SetAtomicInvariantsToUse: AtomicInvariant AtomSymbol must be specified...\n "; 222 } 223 224 # Set atomic invariants... 225 for $AtomicInvariant (keys %{$This->{AtomicInvariantsToUse}}) { 226 $This->{AtomicInvariantsToUse}{$AtomicInvariant} = 0; 227 if (exists $AtomicInvariantsToUse{$AtomicInvariant}) { 228 $This->{AtomicInvariantsToUse}{$AtomicInvariant} = 1; 229 } 230 } 231 232 return $This; 233 } 234 235 # Is it an available AtomicInvariant? 236 # 237 sub IsAtomicInvariantAvailable { 238 my($FirstParameter, $SecondParameter) = @_; 239 my($This, $AtomicInvariant, $Status); 240 241 if ((@_ == 2) && (_IsAtomicInvariantsAtomTypes($FirstParameter))) { 242 ($This, $AtomicInvariant) = ($FirstParameter, $SecondParameter); 243 } 244 else { 245 $AtomicInvariant = $FirstParameter; 246 } 247 $Status = exists($AvailableAtomicInvariants{$AtomicInvariant}) || exists($AvailableAtomicInvariantsByDescription{$AtomicInvariant}) ? 1 : 0; 248 249 return $Status; 250 } 251 252 # Get a hash containing available atomic invariants and their description 253 # as key/value pairs. 254 # 255 sub GetAvailableAtomicInvariants { 256 return %AvailableAtomicInvariants; 257 } 258 259 # Get an array containing order of atomic invariants used to generate atom types... 260 # 261 sub GetAtomicInvariantsOrder { 262 return @AtomicInvariantsOrder; 263 } 264 265 # Assign atom types to all atoms... 266 # 267 # Let: 268 # AS = Atom symbol corresponding to element symbol 269 # 270 # X<n> = Number of non-hydrogen atom neighbors or heavy atoms attached to atom 271 # BO<n> = Sum of bond orders to non-hydrogen atom neighbors or heavy atoms attached to atom 272 # LBO<n> = Largest bond order of non-hydrogen atom neighbors or heavy atoms attached to atom 273 # SB<n> = Number of single bonds to non-hydrogen atom neighbors or heavy atoms attached to atom 274 # DB<n> = Number of double bonds to non-hydrogen atom neighbors or heavy atoms attached to atom 275 # TB<n> = Number of triple bonds to non-hydrogen atom neighbors or heavy atoms attached to atom 276 # H<n> = Number of implicit and explicit hydrogens for atom 277 # Ar = Aromatic annotation indicating whether atom is aromatic 278 # RA = Ring atom annotation indicating whether atom is a ring 279 # FC<+n/-n> = Formal charge assigned to atom 280 # MN<n> = Mass number indicating isotope other than most abundant isotope 281 # SM<n> = Spin multiplicity of atom. Possible values: 1 (singlet), 2 (doublet) or 3 (triplet) 282 # 283 # Then: 284 # 285 # AtomType specification corresponds to: 286 # 287 # AS.X<n>.BO<n>.LBO<n>.<SB><n>.<DB><n>.<TB><n>.H<n>.Ar.RA.FC<+n/-n>.MN<n>.SM<n> 288 # 289 # Except for AS which is a required atomic invariant in atom types, all other atomic invariants are 290 # optional. Default atomic invariants used for AtomID are: AS, X<n>, BO<n>, H<n>, FC<+n/-n>. 291 # AtomID specification doesn't include atomic invariants with zero or undefined values. 292 # 293 # Notes: 294 # . AtomicInvariants with zero or undefined values are not shown. 295 # . LBO with value of 1 is not shown. And absence of LBO in AtomTypes implies the largest 296 # bond order value is one. 297 # . SB, DB and TB with values of zero are not shown. 298 # . The difference in BO and X values corresponds to numbed of pi electrons [ Ref 57 ]. 299 # 300 # Examples of atomic invariant atom types: 301 # 302 # O.X1.BO1.H1 - Hydroxyl oxygen in carboxylate with attached hydrogen and no explicit charge 303 # O.X1.BO1.FC-1 - Hydroxyl ozygen in carboxylate with explicit negative charge 304 # O.X1.BO2 - Carbonyl oxygen in carboxylate with double bond to carbon 305 # O.X2.BO2 - Hydroxyl ozygen in carboxylate attached to carbonyl carbon and another heavy atom 306 # 307 # C.X2.BO3.H1.Ar - Aromatic carbon 308 # 309 sub AssignAtomTypes { 310 my($This) = @_; 311 my($Atom, $AtomType, $AtomicInvariant, $AtomicInvariantValue, @AtomicInvariants); 312 313 ATOM: for $Atom ($This->GetMolecule()->GetAtoms()) { 314 if ($This->{IgnoreHydrogens} && $Atom->IsHydrogen()) { 315 next ATOM; 316 } 317 @AtomicInvariants = (); 318 319 # Go over atomic invariants... 320 ATOMICINVARIANT: for $AtomicInvariant (@AtomicInvariantsOrder) { 321 if (!$This->{AtomicInvariantsToUse}{$AtomicInvariant}) { 322 next ATOMICINVARIANT; 323 } 324 $AtomicInvariantValue = $Atom->GetAtomicInvariantValue($AtomicInvariant); 325 if (!(defined($AtomicInvariantValue) && $AtomicInvariantValue)) { 326 next ATOMICINVARIANT; 327 } 328 if ($AtomicInvariant =~ /^AS$/i) { 329 push @AtomicInvariants, $AtomicInvariantValue; 330 } 331 elsif ($AtomicInvariant =~ /^Ar$/i) { 332 push @AtomicInvariants, "Ar"; 333 } 334 elsif ($AtomicInvariant =~ /^RA$/i) { 335 push @AtomicInvariants, "RA"; 336 } 337 elsif ($AtomicInvariant =~ /^FC$/i) { 338 push @AtomicInvariants, ($AtomicInvariantValue > 0) ? "FC+${AtomicInvariantValue}" : "FC${AtomicInvariantValue}"; 339 } 340 elsif ($AtomicInvariant =~ /^LBO$/i) { 341 if ($AtomicInvariantValue > 1) { 342 push @AtomicInvariants, "${AtomicInvariant}${AtomicInvariantValue}"; 343 } 344 } 345 else { 346 push @AtomicInvariants, "${AtomicInvariant}${AtomicInvariantValue}"; 347 } 348 } 349 # Create and assign atom type to atom... 350 $AtomType = TextUtil::JoinWords(\@AtomicInvariants, ".", 0); 351 $This->SetAtomType($Atom, $AtomType); 352 } 353 return $This; 354 } 355 356 # Are all atoms types successfully assigned? 357 # 358 # Notes: 359 # . Base class method is overridden to always return 1: An appropriate value, atomic invariant 360 # atom types delimited by dot, is always assigned to atoms. 361 # 362 sub IsAtomTypesAssignmentSuccessful { 363 my($This) = @_; 364 365 return 1; 366 } 367 368 # Return a string containg data for AtomicInvariantsAtomTypes object... 369 # 370 sub StringifyAtomicInvariantsAtomTypes { 371 my($This) = @_; 372 my($AtomTypesString); 373 374 # Type of AtomTypes... 375 $AtomTypesString = "AtomTypes: $This->{Type}; IgnoreHydrogens: " . ($This->{IgnoreHydrogens} ? "Yes" : "No"); 376 377 # AvailableAtomicInvariants and AtomicInvariantsToUse... 378 my($AtomicInvariant, @AtomicInvariants, @AtomicInvariantsToUse); 379 380 @AtomicInvariantsToUse = (); 381 @AtomicInvariants = (); 382 for $AtomicInvariant (@AtomicInvariantsOrder) { 383 push @AtomicInvariants, "$AtomicInvariant: $AvailableAtomicInvariants{$AtomicInvariant}"; 384 if ($This->{AtomicInvariantsToUse}{$AtomicInvariant}) { 385 push @AtomicInvariantsToUse, $AtomicInvariant; 386 } 387 } 388 $AtomTypesString .= "; AtomicInvariantsToUse: <" . TextUtil::JoinWords(\@AtomicInvariantsToUse, ", ", 0) . ">"; 389 $AtomTypesString .= "; AtomicInvariantsOrder: <" . TextUtil::JoinWords(\@AtomicInvariantsOrder, ", ", 0) . ">"; 390 $AtomTypesString .= "; AvailableAtomicInvariants: <" . TextUtil::JoinWords(\@AtomicInvariants, ", ", 0) . ">"; 391 392 # Setup atom types information... 393 my($AtomID, $AtomType, @AtomTypesInfo, %AssignedAtomTypes); 394 395 @AtomTypesInfo = (); 396 %AssignedAtomTypes = $This->GetAtomTypes(); 397 398 for $AtomID (sort { $a <=> $b } keys %AssignedAtomTypes) { 399 $AtomType = $AssignedAtomTypes{$AtomID} ? $AssignedAtomTypes{$AtomID} : 'None'; 400 push @AtomTypesInfo, "$AtomID:$AtomType"; 401 } 402 $AtomTypesString .= "; AtomIDs:AtomTypes: <" . TextUtil::JoinWords(\@AtomTypesInfo, ", ", 0) . ">"; 403 404 return $AtomTypesString; 405 } 406 407 # Is it a AtomicInvariantsAtomTypes object? 408 sub _IsAtomicInvariantsAtomTypes { 409 my($Object) = @_; 410 411 return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; 412 } 413