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