MayaChemTools

   1 package Fingerprints::TopologicalAtomTorsionsFingerprints;
   2 #
   3 # File: TopologicalAtomTorsionsFingerprints.pm
   4 # Author: Manish Sud <msud@san.rr.com>
   5 #
   6 # Copyright (C) 2024 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 Fingerprints::Fingerprints;
  30 use TextUtil ();
  31 use Molecule;
  32 use AtomTypes::AtomicInvariantsAtomTypes;
  33 use AtomTypes::DREIDINGAtomTypes;
  34 use AtomTypes::EStateAtomTypes;
  35 use AtomTypes::FunctionalClassAtomTypes;
  36 use AtomTypes::MMFF94AtomTypes;
  37 use AtomTypes::SLogPAtomTypes;
  38 use AtomTypes::SYBYLAtomTypes;
  39 use AtomTypes::TPSAAtomTypes;
  40 use AtomTypes::UFFAtomTypes;
  41 
  42 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  43 
  44 @ISA = qw(Fingerprints::Fingerprints Exporter);
  45 @EXPORT = qw();
  46 @EXPORT_OK = qw();
  47 
  48 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  49 
  50 # Setup class variables...
  51 my($ClassName);
  52 _InitializeClass();
  53 
  54 # Overload Perl functions...
  55 use overload '""' => 'StringifyTopologicalAtomTorsionsFingerprints';
  56 
  57 # Class constructor...
  58 sub new {
  59   my($Class, %NamesAndValues) = @_;
  60 
  61   # Initialize object...
  62   my $This = $Class->SUPER::new();
  63   bless $This, ref($Class) || $Class;
  64   $This->_InitializeTopologicalAtomTorsionsFingerprints();
  65 
  66   $This->_InitializeTopologicalAtomTorsionsFingerprintsProperties(%NamesAndValues);
  67 
  68   return $This;
  69 }
  70 
  71 # Initialize object data...
  72 #
  73 sub _InitializeTopologicalAtomTorsionsFingerprints {
  74   my($This) = @_;
  75 
  76   # Type of fingerprint...
  77   $This->{Type} = 'TopologicalAtomTorsions';
  78 
  79   # Type of vector...
  80   $This->{VectorType} = 'FingerprintsVector';
  81 
  82   # Type of FingerprintsVector...
  83   $This->{FingerprintsVectorType} = 'NumericalValues';
  84 
  85   # Atom identifier type to use for atom IDs in atom torsions...
  86   #
  87   # Currently supported values are: AtomicInvariantsAtomTypes, DREIDINGAtomTypes,
  88   # EStateAtomTypes, FunctionalClassAtomTypes, MMFF94AtomTypes, SLogPAtomTypes,
  89   # SYBYLAtomTypes, TPSAAtomTypes, UFFAtomTypes
  90   #
  91   $This->{AtomIdentifierType} = '';
  92 
  93   # Atom types assigned to each heavy atom...
  94   #
  95   %{$This->{AssignedAtomTypes}} = ();
  96 
  97   # Final unique atom torsions...
  98   #
  99   @{$This->{AtomTorsionsIDs}} = ();
 100   %{$This->{AtomTorsionsCount}} = ();
 101 }
 102 
 103 # Initialize class ...
 104 sub _InitializeClass {
 105   #Class name...
 106   $ClassName = __PACKAGE__;
 107 }
 108 
 109 # Initialize object properties....
 110 sub _InitializeTopologicalAtomTorsionsFingerprintsProperties {
 111   my($This, %NamesAndValues) = @_;
 112 
 113   my($Name, $Value, $MethodName);
 114   while (($Name, $Value) = each  %NamesAndValues) {
 115     $MethodName = "Set${Name}";
 116     $This->$MethodName($Value);
 117   }
 118 
 119   # Make sure molecule object was specified...
 120   if (!exists $NamesAndValues{Molecule}) {
 121     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
 122   }
 123   if (!exists $NamesAndValues{AtomIdentifierType}) {
 124     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying AtomIdentifierType...";
 125   }
 126 
 127   $This->_InitializeFingerprintsVector();
 128 
 129   return $This;
 130 }
 131 
 132 # Set atom identifier type..
 133 #
 134 sub SetAtomIdentifierType {
 135   my($This, $IdentifierType) = @_;
 136 
 137   if ($IdentifierType !~ /^(AtomicInvariantsAtomTypes|DREIDINGAtomTypes|EStateAtomTypes|FunctionalClassAtomTypes|MMFF94AtomTypes|SLogPAtomTypes|SYBYLAtomTypes|TPSAAtomTypes|UFFAtomTypes)$/i) {
 138     croak "Error: ${ClassName}->SetAtomIdentifierType: Specified value, $IdentifierType, for AtomIdentifierType is not vaild. Supported types in current release of MayaChemTools: AtomicInvariantsAtomTypes, DREIDINGAtomTypes, EStateAtomTypes, FunctionalClassAtomTypes, MMFF94AtomTypes, SLogPAtomTypes, SYBYLAtomTypes, TPSAAtomTypes, and UFFAtomTypes.";
 139   }
 140 
 141   if ($This->{AtomIdentifierType}) {
 142     croak "Error: ${ClassName}->SeAtomIdentifierType: Can't change intial atom identifier type:  It's already set...";
 143   }
 144 
 145   $This->{AtomIdentifierType} = $IdentifierType;
 146 
 147   # Initialize atom identifier type information...
 148   $This->_InitializeAtomIdentifierTypeInformation();
 149 
 150   return $This;
 151 }
 152 
 153 # Generate fingerprints description...
 154 #
 155 sub GetDescription {
 156   my($This) = @_;
 157 
 158   # Is description explicity set?
 159   if (exists $This->{Description}) {
 160     return $This->{Description};
 161   }
 162 
 163   # Generate fingerprints description...
 164 
 165   return "$This->{Type}:$This->{AtomIdentifierType}";
 166 }
 167 
 168 # Generate topological atom torsions [ Ref 58, Ref 72 ] fingerprints...
 169 #
 170 # Methodology:
 171 #   . Assign atom types to all the atoms.
 172 #   . Generate and count atom torsions.
 173 #
 174 # Notes:
 175 #   . Hydrogen atoms are ignored during the fingerprint generation.
 176 #
 177 sub GenerateFingerprints {
 178   my($This) = @_;
 179 
 180   # Cache appropriate molecule data...
 181   $This->_SetupMoleculeDataCache();
 182 
 183   # Assign atom types to all heavy atoms...
 184   if (!$This->_AssignAtomTypes()) {
 185     carp "Warning: ${ClassName}->GenerateFingerprints: $This->{AtomIdentifierType} fingerprints generation didn't succeed: Couldn't assign valid $This->{AtomIdentifierType} to all atoms...";
 186     return $This;
 187   }
 188 
 189   # Count atom torsions...
 190   $This->_GenerateAndCountAtomTorsions();
 191 
 192   # Set final fingerprints...
 193   $This->_SetFinalFingerprints();
 194 
 195   # Clear cached molecule data...
 196   $This->_ClearMoleculeDataCache();
 197 
 198   return $This;
 199 }
 200 
 201 # Assign appropriate atom types to all heavy atoms...
 202 #
 203 sub _AssignAtomTypes {
 204   my($This) = @_;
 205   my($SpecifiedAtomTypes, $Atom, $AtomID, $IgnoreHydrogens);
 206 
 207   %{$This->{AssignedAtomTypes}} = ();
 208   $IgnoreHydrogens = 1;
 209 
 210   $SpecifiedAtomTypes = undef;
 211 
 212   IDENTIFIERTYPE: {
 213     if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 214       $SpecifiedAtomTypes = new AtomTypes::AtomicInvariantsAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens, 'AtomicInvariantsToUse' => $This->{AtomicInvariantsToUse});
 215       last IDENTIFIERTYPE;
 216     }
 217 
 218     if ($This->{AtomIdentifierType} =~ /^DREIDINGAtomTypes$/i) {
 219       $SpecifiedAtomTypes = new AtomTypes::DREIDINGAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 220       last IDENTIFIERTYPE;
 221     }
 222 
 223     if ($This->{AtomIdentifierType} =~ /^EStateAtomTypes$/i) {
 224       $SpecifiedAtomTypes = new AtomTypes::EStateAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 225       last IDENTIFIERTYPE;
 226     }
 227 
 228     if ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 229       $SpecifiedAtomTypes = new AtomTypes::FunctionalClassAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens, 'FunctionalClassesToUse' => $This->{FunctionalClassesToUse});
 230       last IDENTIFIERTYPE;
 231     }
 232 
 233     if ($This->{AtomIdentifierType} =~ /^MMFF94AtomTypes$/i) {
 234       $SpecifiedAtomTypes = new AtomTypes::MMFF94AtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 235       last IDENTIFIERTYPE;
 236     }
 237 
 238     if ($This->{AtomIdentifierType} =~ /^SLogPAtomTypes$/i) {
 239       $SpecifiedAtomTypes = new AtomTypes::SLogPAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 240       last IDENTIFIERTYPE;
 241     }
 242     if ($This->{AtomIdentifierType} =~ /^SYBYLAtomTypes$/i) {
 243       $SpecifiedAtomTypes = new AtomTypes::SYBYLAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 244       last IDENTIFIERTYPE;
 245     }
 246 
 247     if ($This->{AtomIdentifierType} =~ /^TPSAAtomTypes$/i) {
 248       $SpecifiedAtomTypes = new AtomTypes::TPSAAtomTypes('Molecule' => $This->{Molecule}, 'IgnorePhosphorus' => 0, 'IgnoreSulfur' => 0);
 249       last IDENTIFIERTYPE;
 250     }
 251 
 252     if ($This->{AtomIdentifierType} =~ /^UFFAtomTypes$/i) {
 253       $SpecifiedAtomTypes = new AtomTypes::UFFAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 254       last IDENTIFIERTYPE;
 255     }
 256 
 257     croak "Error: ${ClassName}->_AssignAtomTypes: Unknown atom indentifier type $This->{AtomIdentifierType}...";
 258   }
 259 
 260   # Assign atom types...
 261   $SpecifiedAtomTypes->AssignAtomTypes();
 262 
 263   # Make sure atom types assignment is successful...
 264   if (!$SpecifiedAtomTypes->IsAtomTypesAssignmentSuccessful()) {
 265     return undef;
 266   }
 267 
 268   # Collect assigned atom types...
 269   ATOM: for $Atom (@{$This->{Atoms}}) {
 270     if ($Atom->IsHydrogen()) {
 271       next ATOM;
 272     }
 273     $AtomID = $Atom->GetID();
 274     $This->{AssignedAtomTypes}{$AtomID} = $SpecifiedAtomTypes->GetAtomType($Atom);
 275   }
 276 
 277   return $This;
 278 }
 279 
 280 # Count atom torsions involving non-hydrogen atoms by going over the structurally
 281 # unique atom torsions...
 282 #
 283 sub _GenerateAndCountAtomTorsions {
 284   my($This) = @_;
 285   my($Atom1, $Atom2, $Atom3, $Atom4, $AtomID1, $AtomID2, $AtomID3, $AtomID4, $AtomTorsionID, @Atom1Neighbors, @Atom2Neighbors, @Atom3Neighbors);
 286 
 287   # Setup a hash to track structurally unique atom torsions by atom IDs...
 288   %{$This->{StructurallyUniqueAtomTorsions}} = ();
 289 
 290   ATOM1: for $Atom1 (@{$This->{Atoms}}) {
 291     if ($Atom1->IsHydrogen()) {
 292       next ATOM1;
 293     }
 294     $AtomID1 = $Atom1->GetID();
 295     # Go over Atom1 neighbors other than Atom1...
 296     @Atom1Neighbors = $Atom1->GetNeighbors($Atom1);
 297     ATOM2: for $Atom2 (@Atom1Neighbors) {
 298       if ($Atom2->IsHydrogen()) {
 299         next ATOM2;
 300       }
 301       $AtomID2 = $Atom2->GetID();
 302       # Go over Atom2 neighbors other than Atom1 and Atom2...
 303       @Atom2Neighbors = $Atom2->GetNeighbors($Atom1, $Atom2);
 304       ATOM3: for $Atom3 (@Atom2Neighbors) {
 305         if ($Atom3->IsHydrogen()) {
 306           next ATOM3;
 307         }
 308         $AtomID3 = $Atom3->GetID();
 309         @Atom3Neighbors = $Atom3->GetNeighbors($Atom1, $Atom2, $Atom3);
 310         # Go over Atom3 neighbors other than Atom1, Atom2 and Atom3...
 311         ATOM4: for $Atom4 (@Atom3Neighbors) {
 312           if ($Atom4->IsHydrogen()) {
 313             next ATOM4;
 314           }
 315           $AtomID4 = $Atom4->GetID();
 316 
 317           # Is it a structurally unique torsion?
 318           if (!$This->_IsStructurallyUniqueTorsion($AtomID1, $AtomID2, $AtomID3, $AtomID4)) {
 319             next ATOM4;
 320           }
 321 
 322           # Track structurally unique torsions...
 323           $AtomTorsionID = $This->_GetAtomTorsionID($AtomID1, $AtomID2, $AtomID3, $AtomID4);
 324           if (exists $This->{AtomTorsionsCount}{$AtomTorsionID}) {
 325             $This->{AtomTorsionsCount}{$AtomTorsionID} += 1;
 326           }
 327           else {
 328             $This->{AtomTorsionsCount}{$AtomTorsionID} = 1;
 329           }
 330         }
 331       }
 332     }
 333   }
 334 
 335   return $This;
 336 }
 337 
 338 # Is it a structurally unique torsions?
 339 #
 340 # Notes:
 341 #  . For a torsion to be structurally unique which hasn't already been encountered,
 342 #    all the four atoms involved in the torsion must be new atoms. And this can be
 343 #    simply implemented by tracking the torsions using atom IDs and maintaining a
 344 #    hash of already encountered torsions using lexicographically smaller torsion ID
 345 #    consisting of four atom IDs.
 346 #
 347 sub _IsStructurallyUniqueTorsion {
 348   my($This, @AtomIDs) = @_;
 349   my($TorsionID, $ReverseTorsionID);
 350 
 351   $TorsionID = join "-", @AtomIDs;
 352   $ReverseTorsionID = join "-", reverse @AtomIDs;
 353 
 354   # Use lexicographically smaller string...
 355   if ($ReverseTorsionID lt $TorsionID) {
 356     $TorsionID = $ReverseTorsionID;
 357   }
 358 
 359   if (exists $This->{StructurallyUniqueAtomTorsions}{$TorsionID}) {
 360     return 0;
 361   }
 362 
 363   # Keep track...
 364   $This->{StructurallyUniqueAtomTorsions}{$TorsionID} = 1;
 365 
 366   return 1;
 367 }
 368 
 369 # Get atom torsion ID corresponding to atom types involved in torsion...
 370 #
 371 # Notes:
 372 #  . TorsionID corresponds to assigned atom types of all the four torsion atoms
 373 #    concatenated by hyphen.
 374 #  . TorsionIDs are generated for both forward and backward sequence of atoms
 375 #    in the torsion and keeping the lexicographically smaller TorsionID to keep TorsionID
 376 #    independent of atom ordering.
 377 #
 378 sub _GetAtomTorsionID {
 379   my($This, @AtomIDs) = @_;
 380   my($AtomTorsionID, $ReverseAtomTorsionID, @AtomTypes);
 381 
 382   @AtomTypes = ();
 383   @AtomTypes = map { $This->{AssignedAtomTypes}{$_} } @AtomIDs;
 384 
 385   $AtomTorsionID = join "-", @AtomTypes;
 386   $ReverseAtomTorsionID = join "-", reverse @AtomTypes;
 387 
 388   # Use lexicographically smaller string as ID...
 389   return ($ReverseAtomTorsionID lt $AtomTorsionID) ? $ReverseAtomTorsionID : $AtomTorsionID;
 390 }
 391 
 392 # Set final fingerpritns vector...
 393 #
 394 sub _SetFinalFingerprints {
 395   my($This) = @_;
 396   my($AtomTorsionID, $Value, @Values);
 397 
 398   # Mark successful generation of fingerprints...
 399   $This->{FingerprintsGenerated} = 1;
 400 
 401   @Values = ();
 402   @{$This->{AtomTorsionsIDs}} = ();
 403 
 404   for $AtomTorsionID (sort keys %{$This->{AtomTorsionsCount}}) {
 405     $Value = $This->{AtomTorsionsCount}{$AtomTorsionID};
 406     push @{$This->{AtomTorsionsIDs}}, $AtomTorsionID;
 407     push @Values, $Value;
 408   }
 409 
 410   # Add AtomPairsIDs and values to fingerprint vector...
 411   $This->{FingerprintsVector}->AddValueIDs(\@{$This->{AtomTorsionsIDs}});
 412   $This->{FingerprintsVector}->AddValues(\@Values);
 413 
 414   return $This;
 415 }
 416 
 417 # Get atom torsions IDs corresponding to atom torsions count values in fingerprint
 418 # vector as an array or reference to an array...
 419 #
 420 # AtomTorsionsIDs list differes in molecules and is generated during finalization
 421 # of fingerprints to make sure the fingerprint vector containing count values
 422 # matches the atom torsions array.
 423 #
 424 sub GetAtomTorsionsIDs {
 425   my($This) = @_;
 426 
 427   return wantarray ? @{$This->{AtomTorsionsIDs}} : \@{$This->{AtomTorsionsIDs}};
 428 }
 429 
 430 # Cache  appropriate molecule data...
 431 #
 432 sub _SetupMoleculeDataCache {
 433   my($This) = @_;
 434 
 435   # Get all atoms including hydrogens. The hydrogen atoms are ignored during processing...
 436   @{$This->{Atoms}} = $This->GetMolecule()->GetAtoms();
 437 
 438   return $This;
 439 }
 440 
 441 # Clear cached molecule data...
 442 #
 443 sub _ClearMoleculeDataCache {
 444   my($This) = @_;
 445 
 446   @{$This->{Atoms}} = ();
 447 
 448   return $This;
 449 }
 450 
 451 # Set atomic invariants to use for atom identifiers...
 452 #
 453 sub SetAtomicInvariantsToUse {
 454   my($This, @Values) = @_;
 455   my($FirstValue, $TypeOfFirstValue, $AtomicInvariant, $SpecifiedAtomicInvariant, $AtomicInvariantValue, @SpecifiedAtomicInvariants, @AtomicInvariantsToUse);
 456 
 457   if (!@Values) {
 458     carp "Warning: ${ClassName}->SetAtomicInvariantsToUse: No values specified...";
 459     return;
 460   }
 461 
 462   $FirstValue = $Values[0];
 463   $TypeOfFirstValue = ref $FirstValue;
 464 
 465   @SpecifiedAtomicInvariants = ();
 466   @AtomicInvariantsToUse = ();
 467 
 468   if ($TypeOfFirstValue =~ /^ARRAY/) {
 469     push @SpecifiedAtomicInvariants, @{$FirstValue};
 470   }
 471   else {
 472     push @SpecifiedAtomicInvariants, @Values;
 473   }
 474 
 475   # Make sure specified AtomicInvariants are valid...
 476   for $SpecifiedAtomicInvariant (@SpecifiedAtomicInvariants) {
 477     if (!AtomTypes::AtomicInvariantsAtomTypes::IsAtomicInvariantAvailable($SpecifiedAtomicInvariant)) {
 478       croak "Error: ${ClassName}->SetAtomicInvariantsToUse: Specified atomic invariant, $SpecifiedAtomicInvariant, is not supported...\n ";
 479     }
 480     $AtomicInvariant = $SpecifiedAtomicInvariant;
 481     push @AtomicInvariantsToUse, $AtomicInvariant;
 482   }
 483 
 484   # Set atomic invariants to use...
 485   @{$This->{AtomicInvariantsToUse}} = ();
 486   push @{$This->{AtomicInvariantsToUse}}, @AtomicInvariantsToUse;
 487 
 488   return $This;
 489 }
 490 
 491 # Set functional classes to use for atom identifiers...
 492 #
 493 sub SetFunctionalClassesToUse {
 494   my($This, @Values) = @_;
 495   my($FirstValue, $TypeOfFirstValue, $FunctionalClass, $SpecifiedFunctionalClass, @SpecifiedFunctionalClasses, @FunctionalClassesToUse);
 496 
 497   if (!@Values) {
 498     carp "Warning: ${ClassName}->SetFunctionalClassesToUse: No values specified...";
 499     return;
 500   }
 501 
 502   if ($This->{AtomIdentifierType} !~ /^FunctionalClassAtomTypes$/i) {
 503     carp "Warning: ${ClassName}->SetFunctionalClassesToUse: FunctionalClassesToUse can't be set for InitialAtomIdentifierType of $This->{AtomIdentifierType}...";
 504     return;
 505   }
 506 
 507   $FirstValue = $Values[0];
 508   $TypeOfFirstValue = ref $FirstValue;
 509 
 510   @SpecifiedFunctionalClasses = ();
 511   @FunctionalClassesToUse = ();
 512 
 513   if ($TypeOfFirstValue =~ /^ARRAY/) {
 514     push @SpecifiedFunctionalClasses, @{$FirstValue};
 515   }
 516   else {
 517     push @SpecifiedFunctionalClasses, @Values;
 518   }
 519 
 520   # Make sure specified FunctionalClasses are valid...
 521   for $SpecifiedFunctionalClass (@SpecifiedFunctionalClasses) {
 522     if (!AtomTypes::FunctionalClassAtomTypes::IsFunctionalClassAvailable($SpecifiedFunctionalClass)) {
 523       croak "Error: ${ClassName}->SetFunctionalClassesToUse: Specified functional class, $SpecifiedFunctionalClass, is not supported...\n ";
 524     }
 525     push @FunctionalClassesToUse, $SpecifiedFunctionalClass;
 526   }
 527 
 528   # Set functional classes to use...
 529   @{$This->{FunctionalClassesToUse}} = ();
 530   push @{$This->{FunctionalClassesToUse}}, @FunctionalClassesToUse;
 531 
 532   return $This;
 533 }
 534 
 535 # Initialize atom indentifier type information...
 536 #
 537 # Current supported values:
 538 #
 539 # AtomicInvariantsAtomTypes, DREIDINGAtomTypes, EStateAtomTypes, FunctionalClassAtomTypes,
 540 # MMFF94AtomTypes, SLogPAtomTypes, SYBYLAtomTypes, TPSAAtomTypes, UFFAtomTypes
 541 #
 542 sub _InitializeAtomIdentifierTypeInformation {
 543   my($This) = @_;
 544 
 545   if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 546     $This->_InitializeAtomicInvariantsAtomTypesInformation();
 547   }
 548   elsif ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 549     $This->_InitializeFunctionalClassAtomTypesInformation();
 550   }
 551   elsif ($This->{AtomIdentifierType} =~ /^(DREIDINGAtomTypes|EStateAtomTypes|MMFF94AtomTypes|SLogPAtomTypes|SYBYLAtomTypes|TPSAAtomTypes|UFFAtomTypes)$/i) {
 552     # Nothing to do for now...
 553   }
 554   else {
 555     croak "Error: ${ClassName}->_InitializeAtomIdentifierTypeInformation: Unknown atom indentifier type $This->{AtomIdentifierType}...";
 556   }
 557 
 558   return $This;
 559 }
 560 
 561 # Initialize atomic invariants to use for generating atom IDs in atom torsions...
 562 #
 563 # Let:
 564 #   AS = Atom symbol corresponding to element symbol
 565 #
 566 #   X<n>   = Number of non-hydrogen atom neighbors or heavy atoms attached to atom
 567 #   BO<n> = Sum of bond orders to non-hydrogen atom neighbors or heavy atoms attached to atom
 568 #   LBO<n> = Largest bond order of non-hydrogen atom neighbors or heavy atoms attached to atom
 569 #   SB<n> = Number of single bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 570 #   DB<n> = Number of double bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 571 #   TB<n> = Number of triple bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 572 #   H<n>   = Number of implicit and explicit hydrogens for atom
 573 #   Ar     = Aromatic annotation indicating whether atom is aromatic
 574 #   RA     = Ring atom annotation indicating whether atom is a ring
 575 #   FC<+n/-n> = Formal charge assigned to atom
 576 #   MN<n> = Mass number indicating isotope other than most abundant isotope
 577 #   SM<n> = Spin multiplicity of atom. Possible values: 1 (singlet), 2 (doublet) or 3 (triplet)
 578 #
 579 #   AtomTypeIDx = Atomic invariants atom type for atom x
 580 #   AtomTypeIDy = Atomic invariants atom type for atom y
 581 #   AtomTypeIDz = Atomic invariants atom type for atom z
 582 #   AtomTypeIDw = Atomic invariants atom type for atom w
 583 #
 584 # Then:
 585 #
 586 #   Atom torsion AtomID generated by AtomTypes::AtomicInvariantsAtomTypes class corresponds to:
 587 #
 588 #     AS.X<n>.BO<n>.LBO<n>.<SB><n>.<DB><n>.<TB><n>.H<n>.Ar.RA.FC<+n/-n>.MN<n>.SM<n>
 589 #
 590 #  AtomTorsion ID corresponds to:
 591 #
 592 #    AtomTypeIDx-AtomTypeIDy-AtomTypeIDz-AtomTypeIDw
 593 #
 594 # Except for AS which is a required atomic invariant in atom torsions AtomIDs, all other atomic invariants are
 595 # optional. Default atomic invariants used for AtomID are: AS, X<n>, BO<n>, H<n>, FC<+n/-n>.
 596 # AtomID specification doesn't include atomic invariants with zero or undefined values.
 597 #
 598 # Examples of atom torsion AtomIDs in Aspirin using default atomic invariants:
 599 #
 600 #  C.X1.BO1.H3-C.X3.BO4-O.X2.BO2-C.X3.BO4
 601 #  C.X2.BO3.H1-C.X2.BO3.H1-C.X2.BO3.H1-C.X2.BO3.H1
 602 #  C.X3.BO4-C.X3.BO4-O.X2.BO2-C.X3.BO4
 603 #  C.X3.BO4-O.X2.BO2-C.X3.BO4-O.X1.BO2
 604 #
 605 # Examples of atom torsion AtomIDs in Aspirin using AS, X and BO atomic invariants:
 606 #
 607 #  C.X1.BO1-C.X3.BO4-O.X2.BO2-C.X3.BO4
 608 #  C.X2.BO3-C.X2.BO3-C.X2.BO3-C.X2.BO3
 609 #  C.X3.BO4-C.X3.BO4-O.X2.BO2-C.X3.BO4
 610 #  C.X3.BO4-O.X2.BO2-C.X3.BO4-O.X1.BO2
 611 #
 612 sub _InitializeAtomicInvariantsAtomTypesInformation {
 613   my($This) = @_;
 614 
 615   # Default atomic invariants to use for generating atom torsions atom IDs: AS, X, BO, H, FC
 616   #
 617   @{$This->{AtomicInvariantsToUse}} = ();
 618   @{$This->{AtomicInvariantsToUse}} = ('AS', 'X', 'BO', 'H', 'FC');
 619 
 620   return $This;
 621 }
 622 
 623 # Initialize functional class atom types, generated by AtomTypes::FunctionalClassAtomTypes
 624 # class, to use for generating atom identifiers...
 625 #
 626 # Let:
 627 #   HBD: HydrogenBondDonor
 628 #   HBA: HydrogenBondAcceptor
 629 #   PI :  PositivelyIonizable
 630 #   NI : NegativelyIonizable
 631 #   Ar : Aromatic
 632 #   Hal : Halogen
 633 #   H : Hydrophobic
 634 #   RA : RingAtom
 635 #   CA : ChainAtom
 636 #
 637 # Then:
 638 #
 639 #   Functiononal class atom type specification for an atom corresponds to:
 640 #
 641 #     Ar.CA.H.HBA.HBD.Hal.NI.PI.RA
 642 #
 643 #   Default functional classes used are: HBD, HBA, PI, NI, Ar, Hal
 644 #
 645 #   FunctionalAtomTypes are assigned using the following definitions [ Ref 60-61, Ref 65-66 ]:
 646 #
 647 #     HydrogenBondDonor: NH, NH2, OH
 648 #     HydrogenBondAcceptor: N[!H], O
 649 #     PositivelyIonizable: +, NH2
 650 #     NegativelyIonizable: -, C(=O)OH, S(=O)OH, P(=O)OH
 651 #
 652 sub _InitializeFunctionalClassAtomTypesInformation {
 653   my($This) = @_;
 654 
 655   # Default functional class atom typess to use for generating atom identifiers
 656   # are: HBD, HBA, PI, NI, Ar, Hal
 657   #
 658   @{$This->{FunctionalClassesToUse}} = ();
 659   @{$This->{FunctionalClassesToUse}} = ('HBD', 'HBA', 'PI', 'NI', 'Ar', 'Hal');
 660 
 661   return $This;
 662 }
 663 
 664 # Return a string containg data for TopologicalAtomTorsionsFingerprints object...
 665 #
 666 sub StringifyTopologicalAtomTorsionsFingerprints {
 667   my($This) = @_;
 668   my($FingerprintsString);
 669 
 670   # Type of fingerprint...
 671   $FingerprintsString = "Fingerprint type: $This->{Type}; AtomIdentifierType: $This->{AtomIdentifierType}";
 672 
 673   if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 674     my($AtomicInvariant, @AtomicInvariants, @AtomicInvariantsOrder, %AvailableAtomicInvariants);
 675 
 676     @AtomicInvariantsOrder = AtomTypes::AtomicInvariantsAtomTypes::GetAtomicInvariantsOrder();
 677     %AvailableAtomicInvariants = AtomTypes::AtomicInvariantsAtomTypes::GetAvailableAtomicInvariants();
 678 
 679     for $AtomicInvariant (@AtomicInvariantsOrder) {
 680       push @AtomicInvariants, "$AtomicInvariant: $AvailableAtomicInvariants{$AtomicInvariant}";
 681     }
 682 
 683     $FingerprintsString .= "; AtomicInvariantsToUse: <" . TextUtil::JoinWords(\@{$This->{AtomicInvariantsToUse}}, ", ", 0) . ">";
 684     $FingerprintsString .= "; AtomicInvariantsOrder: <" . TextUtil::JoinWords(\@AtomicInvariantsOrder, ", ", 0) . ">";
 685     $FingerprintsString .= "; AvailableAtomicInvariants: <" . TextUtil::JoinWords(\@AtomicInvariants, ", ", 0) . ">";
 686   }
 687   elsif ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 688     my($FunctionalClass, @FunctionalClasses, @FunctionalClassesOrder, %AvailableFunctionalClasses);
 689 
 690     @FunctionalClassesOrder = AtomTypes::FunctionalClassAtomTypes::GetFunctionalClassesOrder();
 691     %AvailableFunctionalClasses = AtomTypes::FunctionalClassAtomTypes::GetAvailableFunctionalClasses();
 692 
 693     for $FunctionalClass (@FunctionalClassesOrder) {
 694       push @FunctionalClasses, "$FunctionalClass: $AvailableFunctionalClasses{$FunctionalClass}";
 695     }
 696 
 697     $FingerprintsString .= "; FunctionalClassesToUse: <" . TextUtil::JoinWords(\@{$This->{FunctionalClassesToUse}}, ", ", 0) . ">";
 698     $FingerprintsString .= "; FunctionalClassesOrder: <" . TextUtil::JoinWords(\@FunctionalClassesOrder, ", ", 0) . ">";
 699     $FingerprintsString .= "; AvailableFunctionalClasses: <" . TextUtil::JoinWords(\@FunctionalClasses, ", ", 0) . ">";
 700   }
 701 
 702   # Total number of atom torsions...
 703   $FingerprintsString .= "; NumOfAtomTorsions: " . $This->{FingerprintsVector}->GetNumOfValues();
 704 
 705   # FingerprintsVector...
 706   $FingerprintsString .= "; FingerprintsVector: < $This->{FingerprintsVector} >";
 707 
 708   return $FingerprintsString;
 709 }
 710