       1 package Fingerprints::TopologicalPharmacophoreAtomTripletsFingerprints;
       2 #
       3 # File: TopologicalPharmacophoreAtomTripletsFingerprints.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 Fingerprints::Fingerprints;
      30 use TextUtil ();
      31 use MathUtil ();
      32 use Molecule;
      33 use AtomTypes::FunctionalClassAtomTypes;
      34
      35 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
      36
      37 @ISA = qw(Fingerprints::Fingerprints Exporter);
      38 @EXPORT = qw();
      39 @EXPORT_OK = qw();
      40
      41 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
      42
      43 # Setup class variables...
      44 my($ClassName);
      45 _InitializeClass();
      46
      47 # Overload Perl functions...
      48 use overload '""' => 'StringifyTopologicalPharmacophoreAtomTripletsFingerprints';
      49
      50 # Class constructor...
      51 sub new {
      52   my($Class, %NamesAndValues) = @_;
      53
      54   # Initialize object...
      55   my $This = $Class->SUPER::new();
      56   bless $This, ref($Class) || $Class;
      57   $This->_InitializeTopologicalPharmacophoreAtomTripletsFingerprints();
      58
      59   $This->_InitializeTopologicalPharmacophoreAtomTripletsFingerprintsProperties(%NamesAndValues);
      60
      61   return $This;
      62 }
      63
      64 # Initialize object data...
      65 #
      66 sub _InitializeTopologicalPharmacophoreAtomTripletsFingerprints {
      67   my($This) = @_;
      68
      69   # Type of fingerprint...
      70   $This->{Type} = 'TopologicalPharmacophoreAtomTriplets';
      71
      72   # Type of vector...
      73   $This->{VectorType} = 'FingerprintsVector';
      74
      75   # AtomTripletsSetSizeToUse...
      76   #
      77   # ArbitrarySize - Corrresponds to atom triplets with non-zero count
      78   # FixedSize - Corresponds to all atom triplets with zero and non-zero count
      79   #
      80   # Possible values: ArbitrarySize or FixedSize. Default: ArbitrarySize
      81   #
      82   $This->{AtomTripletsSetSizeToUse} = '';
      83
      84   #
      85   # OrderedNumericalValues - For ArbitrarySize value of AtomTripletsSetSizeToUse
      86   # NumericalValues - For FixedSize value of AtomTripletsSetSizeToUse
      87   #
      88   # Possible values: OrderedNumericalValues or NumericalValues. Default: NumericalValues
      89   #
      90   $This->{FingerprintsVectorType} = '';
      91
      92   # Minimum and maximum bond distance between pharmacophore atom pairs corresponding to
      93   # atom triplets and distance bin size used for binning distances.
      94   #
      95   # In order to distribute distance bins of equal size, the last bin is allowed to go past the
      96   # maximum distance specified by upto distance bin size.
      97   #
      98   # The default MinDistance and MaxDistance values of 1 and 10 with DistanceBinSize of
      99   # 2 [ Ref 70 ] generates the following 5 distance bins: [1, 2] [3, 4] [5, 6] [7, 8] [9 10]
     100   #
     101   $This->{MinDistance} = 1;
     102   $This->{MaxDistance} = 10;
     103
     104   # Distance bin size used for binning distances...
     105   #
     106   $This->{DistanceBinSize} = 2;
     107
     108   # Determines whether to apply triangle inequality to distances triplets during basis set generation...
     109   #
     110   $This->{UseTriangleInequality} = 1;
     111
     112   # Initialize pharmacophore atom types information...
     113   $This->_InitializeToplogicalPharmacophoreAtomTypesInformation();
     114
     115   # Pharmacophore types assigned to each heavy atom...
     116   #
     117   %{$This->{AssignedAtomTypes}} = ();
     118
     119   # All pharmacophore atom triplets between minimum and maximum distance...
     120   #
     121   %{$This->{AtomTriplets}} = ();
     122   @{$This->{AtomTriplets}{IDs}} = ();
     123   %{$This->{AtomTriplets}{Count}} = ();
     124 }
     125
     126 # Initialize class ...
     127 sub _InitializeClass {
     128   #Class name...
     129   $ClassName = __PACKAGE__;
     130 }
     131
     132 # Initialize object properties....
     133 sub _InitializeTopologicalPharmacophoreAtomTripletsFingerprintsProperties {
     134   my($This, %NamesAndValues) = @_;
     135
     136   my($Name, $Value, $MethodName);
     137   while (($Name, $Value) = each  %NamesAndValues) {
     138     $MethodName = "Set${Name}";
     139     $This->$MethodName($Value);
     140   }
     141
     142   # Make sure molecule object was specified...
     143   if (!exists $NamesAndValues{Molecule}) {
     144     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
     145   }
     146   $This->_InitializeTopologicalPharmacophoreAtomTripletsFingerprintsVector();
     147
     148   return $This;
     149 }
     150
     151 # Initialize fingerprints vector...
     152 #
     153 sub _InitializeTopologicalPharmacophoreAtomTripletsFingerprintsVector {
     154   my($This) = @_;
     155
     156   if (!$This->{AtomTripletsSetSizeToUse}) {
     157     $This->{AtomTripletsSetSizeToUse} =  'ArbitrarySize';
     158   }
     159
     160   # Vector type and type of values...
     161   $This->{VectorType} = 'FingerprintsVector';
     162
     163   if ($This->{AtomTripletsSetSizeToUse} =~ /^FixedSize$/i) {
     164     $This->{FingerprintsVectorType} = 'OrderedNumericalValues';
     165   }
     166   else {
     167     $This->{FingerprintsVectorType} = 'NumericalValues';
     168   }
     169
     170   $This->_InitializeFingerprintsVector();
     171 }
     172
     173 # Set atom parits set size to use...
     174 #
     175 sub SetAtomTripletsSetSizeToUse {
     176   my($This, $Value) = @_;
     177
     178   if ($This->{AtomTripletsSetSizeToUse}) {
     179     croak "Error: ${ClassName}->SetAtomTripletsSetSizeToUse: Can't change size:  It's already set...";
     180   }
     181
     182   if ($Value !~ /^(ArbitrarySize|FixedSize)$/i) {
     183     croak "Error: ${ClassName}->SetAtomTripletsSetSizeToUse: Unknown AtomTripletsSetSizeToUse value: $Value; Supported values: ArbitrarySize or FixedSize";
     184   }
     185
     186   $This->{AtomTripletsSetSizeToUse} = $Value;
     187
     188   return $This;
     189 }
     190
     191 # Initialize topological atom types, generated by AtomTypes::FunctionalClassAtomTypes
     192 # class, to use for atom triplets fingerprint generation...
     193 #
     194 # Let:
     195 #   HBD: HydrogenBondDonor
     196 #   HBA: HydrogenBondAcceptor
     197 #   PI :  PositivelyIonizable
     198 #   NI : NegativelyIonizable
     199 #   Ar : Aromatic
     200 #   Hal : Halogen
     201 #   H : Hydrophobic
     202 #   RA : RingAtom
     203 #   CA : ChainAtom
     204 #
     205 # Then:
     206 #
     207 #   Functiononal class atom type specification for an atom corresponds to:
     208 #
     209 #     Ar.CA.H.HBA.HBD.Hal.NI.PI.RA
     210 #
     211 #   Default pharmacophore atom types [ Ref 71 ] to use for atom triplets fingerprint generation
     212 #   are: HBD, HBA, PI, NI, H, Ar
     213 #
     214 #   FunctionalAtomTypes are assigned using the following definitions [ Ref 60-61, Ref 65-66 ]:
     215 #
     216 #     HydrogenBondDonor: NH, NH2, OH
     217 #     HydrogenBondAcceptor: N[!H], O
     218 #     PositivelyIonizable: +, NH2
     219 #     NegativelyIonizable: -, C(=O)OH, S(=O)OH, P(=O)OH
     220 #
     221 sub _InitializeToplogicalPharmacophoreAtomTypesInformation {
     222   my($This) = @_;
     223
     224   #   Default pharmacophore atom types to use for atom triplets fingerprint generation
     225   #   are: HBD, HBA, PI, NI, H, Ar
     226   #
     227   @{$This->{AtomTypesToUse}} = ();
     228   @{$This->{AtomTypesToUse}} = sort ('HBD', 'HBA', 'PI', 'NI', 'H', 'Ar');
     229
     230   return $This;
     231 }
     232
     233 # Set atom types to use for atom triplets...
     234 #
     235 sub SetAtomTypesToUse {
     236   my($This, @Values) = @_;
     237   my($FirstValue, $TypeOfFirstValue, $AtomType, $SpecifiedAtomType, @SpecifiedAtomTypes, @AtomTypesToUse);
     238
     239   if (!@Values) {
     240     carp "Warning: ${ClassName}->SetAtomTypesToUse: No values specified...";
     241     return;
     242   }
     243
     244   $FirstValue = $Values[0];
     245   $TypeOfFirstValue = ref $FirstValue;
     246
     247   @SpecifiedAtomTypes = ();
     248   @AtomTypesToUse = ();
     249
     250   if ($TypeOfFirstValue =~ /^ARRAY/) {
     251     push @SpecifiedAtomTypes, @{$FirstValue};
     252   }
     253   else {
     254     push @SpecifiedAtomTypes, @Values;
     255   }
     256
     257   # Make sure specified AtomTypes are valid...
     258   for $SpecifiedAtomType (@SpecifiedAtomTypes) {
     259     if (!AtomTypes::FunctionalClassAtomTypes::IsFunctionalClassAvailable($SpecifiedAtomType)) {
     260       croak "Error: ${ClassName}->SetAtomTypesToUse: Specified atom type, $SpecifiedAtomType, is not supported...\n ";
     261     }
     262     $AtomType = $SpecifiedAtomType;
     263     push @AtomTypesToUse, $AtomType;
     264   }
     265
     266   # Set atom types to use...
     267   @{$This->{AtomTypesToUse}} = ();
     268   push @{$This->{AtomTypesToUse}}, sort @AtomTypesToUse;
     269
     270   return $This;
     271 }
     272
     273 # Set minimum distance for pharmacophore atom pairs in atom triplets...
     274 #
     275 sub SetMinDistance {
     276   my($This, $Value) = @_;
     277
     278   if (!TextUtil::IsPositiveInteger($Value)) {
     279     croak "Error: ${ClassName}->SetMinDistance: MinDistance value, $Value, is not valid:  It must be a positive integer...";
     280   }
     281   $This->{MinDistance} = $Value;
     282
     283   return $This;
     284 }
     285
     286 # Set maximum distance for pharmacophore atom pairs in atom triplets...
     287 #
     288 sub SetMaxDistance {
     289   my($This, $Value) = @_;
     290
     291   if (!TextUtil::IsPositiveInteger($Value)) {
     292     croak "Error: ${ClassName}->SetMaxDistance: MaxDistance value, $Value, is not valid:  It must be a positive integer...";
     293   }
     294   $This->{MaxDistance} = $Value;
     295
     296   return $This;
     297 }
     298
     299 # Set distance bin size for binning pharmacophore atom pair distances in atom triplets...
     300 #
     301 sub SetDistanceBinSize {
     302   my($This, $Value) = @_;
     303
     304   if (!TextUtil::IsPositiveInteger($Value)) {
     305     croak "Error: ${ClassName}->SetDistanceBinSize: DistanceBinSize value, $Value, is not valid:  It must be a positive integer...";
     306   }
     307   $This->{DistanceBinSize} = $Value;
     308
     309   return $This;
     310 }
     311
     312 # Generate fingerprints description...
     313 #
     314 sub GetDescription {
     315   my($This) = @_;
     316
     317   # Is description explicity set?
     318   if (exists $This->{Description}) {
     319     return $This->{Description};
     320   }
     321
     322   # Generate fingerprints description...
     323
     324   return "$This->{Type}:$This->{AtomTripletsSetSizeToUse}:MinDistance$This->{MinDistance}:MaxDistance$This->{MaxDistance}";
     325 }
     326
     327 # Generate topological pharmacophore atom triplets [ Ref 66, Ref 68-71 ]  fingerprints...
     328 #
     329 # Let:
     330 #
     331 #   P = Any of the supported pharmacophore atom types
     332 #
     333 #   Px = Pharmacophore atom x
     334 #   Py = Pharmacophore atom y
     335 #   Pz = Pharmacophore atom z
     336 #
     337 #   Dxy = Distance or lower bound of binned distance between Px and Py
     338 #   Dxz = Distance or lower bound of binned distance between Px and Pz
     339 #   Dyz = Distance or lower bound of binned distance between Py and Pz
     340 #
     341 # Then:
     342 #   PxDyz-PyDxz-PzDxy = Pharmacophore atom triplet ID for atoms Px, Py and Pz
     343 #
     344 # For example: H1-H1-H1, H2-HBA-H2 and so on
     345 #
     346 # Methodology:
     347 #   . Generate a distance matrix.
     348 #   . Using specified minimum, maximum and distance bin size, generate a binned distance
     349 #     matrix from distance matrix. The lower distance bound on the distance bin is used
     350 #     in the binned distance matrix and atom triplet IDs.
     351 #   . Assign pharmacophore atom types to all the atoms.
     352 #   . Initialize pharmacophore atom triplets basis set for all unique triplets constituting
     353 #     atom pairs binned distances between minimum and maximum distance.
     354 #       . Optionally, trinagle inequality is also implied which means:
     355 #         . Distance or binned distance between any two pairs in a triplet must be less than the
     356 #            sum of distances or binned distances between other two pairs and greater than the
     357 #            difference of distances between other pairs.
     358 #   . Using binned distance matrix and pharmacophore atom types, count occurance of
     359 #     unique atom triplets.
     360 #
     361 # Notes:
     362 #   . Hydrogen atoms are ignored during the fingerprint generation.
     363 #
     364 sub GenerateFingerprints {
     365   my($This) = @_;
     366
     367   if ($This->{MinDistance} > $This->{MaxDistance}) {
     368     croak "Error: ${ClassName}->GenerateTopologicalPharmacophoreAtomTripletsFingerprints: No fingerpritns generated: MinDistance, $This->{MinDistance}, must be <= MaxDistance, $This->{MaxDistance}...";
     369   }
     370
     371   # Cache appropriate molecule data...
     372   $This->_SetupMoleculeDataCache();
     373
     374   # Generate distance matrix...
     375   if (!$This->_SetupDistanceMatrix()) {
     376     carp "Warning: ${ClassName}->GenerateFingerprints: Fingerprints generation didn't succeed: Couldn't generate distance matrix...";
     377     return $This;
     378   }
     379
     380   # Generate binned distance matrix...
     381   $This->_GenerateBinnedDistanceMatrix();
     382
     383   # Assign pharmacohore atom types to all heavy atoms...
     384   $This->_AssignPharmacophoreAtomTypes();
     385
     386   # Initialize values of all possible pharmacohore atom triplets...
     387   $This->_InitializePharmacophoreAtomTriplets();
     388
     389   # Count atom triplets...
     390   $This->_CountPharmacohoreAtomTriplets();
     391
     392   # Set final fingerprints...
     393   $This->_SetFinalFingerprints();
     394
     395   # Clear cached molecule data...
     396   $This->_ClearMoleculeDataCache();
     397
     398   return $This;
     399 }
     400
     401 # Setup distance matrix...
     402 #
     403 sub _SetupDistanceMatrix {
     404   my($This) = @_;
     405
     406   $This->{DistanceMatrix} = $This->GetMolecule()->GetDistanceMatrix();
     407
     408   if (!$This->{DistanceMatrix}) {
     409     return undef;
     410   }
     411
     412   return $This;
     413 }
     414
     415 # Generate binned distance matrix for distances with in the specified distance ranges...
     416 #
     417 sub _GenerateBinnedDistanceMatrix {
     418   my($This) = @_;
     419   my($DistanceMatrix, $BinnedDistanceMatrix, $NumOfRows, $NumOfCols, $RowIndex, $ColIndex, $SkipIndexCheck);
     420
     421   $DistanceMatrix = $This->{DistanceMatrix};
     422   ($NumOfRows, $NumOfCols) = $DistanceMatrix->GetSize();
     423
     424   # Initialize binned distance matrix...
     425   $BinnedDistanceMatrix = new Matrix($NumOfRows, $NumOfCols);
     426
     427   # Setup distance to binned distance map...
     428   my($BinnedDistance, $Distance, %DistanceToBinnedDistance);
     429   %DistanceToBinnedDistance = ();
     430   for ($BinnedDistance = $This->{MinDistance}; $BinnedDistance <= $This->{MaxDistance}; $BinnedDistance += $This->{DistanceBinSize}) {
     431     for $Distance ($BinnedDistance .. ($BinnedDistance + $This->{DistanceBinSize} - 1)) {
     432       $DistanceToBinnedDistance{$Distance} = $BinnedDistance;
     433     }
     434   }
     435
     436   # Generate binned distance matrix...
     437   $SkipIndexCheck = 0;
     438   for $RowIndex (0 .. ($NumOfRows - 1) ) {
     439     COLINDEX: for $ColIndex (($RowIndex + 1) .. ($NumOfCols - 1) ) {
     440       $Distance = $DistanceMatrix->GetValue($RowIndex, $ColIndex, $SkipIndexCheck);
     441       if ($Distance < $This->{MinDistance} || $Distance > $This->{MaxDistance}) {
     442         next COLINDEX;
     443       }
     444       $BinnedDistance = $DistanceToBinnedDistance{$Distance};
     445       $BinnedDistanceMatrix->SetValue($RowIndex, $ColIndex, $BinnedDistance, $SkipIndexCheck);
     446       $BinnedDistanceMatrix->SetValue($ColIndex, $RowIndex, $BinnedDistance, $SkipIndexCheck);
     447     }
     448   }
     449
     450   $This->{BinnedDistanceMatrix} = $BinnedDistanceMatrix;
     451
     452   return $This;
     453 }
     454
     455 # Assign pharmacohore atom types to all heavy atoms...
     456 #
     457 sub _AssignPharmacophoreAtomTypes {
     458   my($This) = @_;
     459   my($Atom, $AtomID, $AtomType, $FunctionalClassAtomTypes);
     460
     461   # Assign topological pharmacophore atom types...
     462   $FunctionalClassAtomTypes = new AtomTypes::FunctionalClassAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => 1, 'FunctionalClassesToUse' => $This->{AtomTypesToUse});
     463   $FunctionalClassAtomTypes->AssignAtomTypes();
     464
     465   %{$This->{AssignedAtomTypes}} = ();
     466
     467   ATOM: for $Atom (@{$This->{Atoms}}) {
     468     if ($Atom->IsHydrogen()) {
     469       next ATOM;
     470     }
     471     $AtomID = $Atom->GetID();
     472
     473     my(@AtomTypes);
     474     @AtomTypes = ();
     475
     476     $AtomType = $FunctionalClassAtomTypes->GetAtomType($Atom);
     477     if ($AtomType && $AtomType !~ /^None$/i) {
     478       push @AtomTypes, split /\./, $AtomType;
     479     }
     480     # Assign phramacophore types list to atom...
     481     $This->{AssignedAtomTypes}{$AtomID} = \@AtomTypes;
     482   }
     483   return $This;
     484 }
     485
     486 # Initialize pharmacophore atom triplets basis set for all unique triplets constituting atom pairs
     487 # binned distances between minimum and maximum distance and optionally applying triangle
     488 # inequality. The DistanceBinSize determines the size of the distance bins. The lower distance
     489 # bound, along with specified pharmacophore types, is used during generation of atom triplet
     490 # IDs.
     491 #
     492 #
     493 sub _InitializePharmacophoreAtomTriplets {
     494   my($This) = @_;
     495   my($AtomType1, $AtomType2, $AtomType3, $BinnedDistance12, $BinnedDistance13, $BinnedDistance23, $AtomTripletID);
     496
     497   # Initialize atom triplets information...
     498   for ($BinnedDistance12 = $This->{MinDistance}; $BinnedDistance12 <= $This->{MaxDistance}; $BinnedDistance12 += $This->{DistanceBinSize}) {
     499     for ($BinnedDistance13 = $This->{MinDistance}; $BinnedDistance13 <= $This->{MaxDistance}; $BinnedDistance13 += $This->{DistanceBinSize}) {
     500       DISTANCE23: for ($BinnedDistance23 = $BinnedDistance12; $BinnedDistance23 <= $This->{MaxDistance}; $BinnedDistance23 += $This->{DistanceBinSize}) {
     501         if ($This->{UseTriangleInequality} && !$This->_DoDistancesSatisfyTriangleInequality($BinnedDistance12, $BinnedDistance13, $BinnedDistance23)) {
     502           next DISTANCE23;
     503         }
     504         for $AtomType1 (@{$This->{AtomTypesToUse}}) {
     505           for $AtomType2 (@{$This->{AtomTypesToUse}}) {
     506             ATOMTYPE3: for $AtomType3 (@{$This->{AtomTypesToUse}}) {
     507               $AtomTripletID = $This->_GetAtomTripletID($AtomType1, $BinnedDistance23, $AtomType2, $BinnedDistance13, $AtomType3, $BinnedDistance12);
     508               if (exists $This->{AtomTriplets}{Count}{$AtomTripletID}) {
     509                       next ATOMTYPE3;
     510               }
     511               # Unique atom triplets information...
     512               push @{$This->{AtomTriplets}{IDs}}, $AtomTripletID;
     513               $This->{AtomTriplets}{Count}{$AtomTripletID} = 0;
     514             }
     515           }
     516         }
     517       }
     518     }
     519   }
     520   return $This;
     521 }
     522
     523 # Check triangle inequality...
     524 #
     525 sub _DoDistancesSatisfyTriangleInequality {
     526   my($This, $Distance1, $Distance2, $Distance3) = @_;
     527
     528   if ( !($Distance1 > abs($Distance2 - $Distance3) && $Distance1 < ($Distance2 + $Distance3)) ) {
     529     return 0;
     530   }
     531   if ( !($Distance2 > abs($Distance1 - $Distance3) && $Distance2 < ($Distance1 + $Distance3)) ) {
     532     return 0;
     533   }
     534   if ( !($Distance3 > abs($Distance1 - $Distance2) && $Distance3 < ($Distance1 + $Distance2)) ) {
     535     return 0;
     536   }
     537   return 1;
     538 }
     539
     540 # Count pharmacophore atom triplets...
     541 #
     542 sub _CountPharmacohoreAtomTriplets {
     543   my($This) = @_;
     544   my($NumOfAtoms, $AtomIndex1, $AtomIndex2, $AtomIndex3, $AtomID1, $AtomID2, $AtomID3, $AtomType1, $AtomType2, $AtomType3, $BinnedDistance12, $BinnedDistance13, $BinnedDistance23, $SkipIndexCheck, $BinnedDistanceMatrix, $AtomTripletID);
     545
     546   $NumOfAtoms = @{$This->{Atoms}};
     547   $BinnedDistanceMatrix = $This->{BinnedDistanceMatrix};
     548   $SkipIndexCheck = 0;
     549
     550   ATOMINDEX1: for $AtomIndex1 (0 .. ($NumOfAtoms - 1)) {
     551     $AtomID1 = $This->{AtomIndexToID}{$AtomIndex1};
     552     if ( !((exists($This->{AssignedAtomTypes}{$AtomID1}) && @{$This->{AssignedAtomTypes}{$AtomID1}})) ) {
     553       next ATOMINDEX1;
     554     }
     555
     556     ATOMINDEX2: for $AtomIndex2 (($AtomIndex1 + 1) .. ($NumOfAtoms - 1)) {
     557       $AtomID2 = $This->{AtomIndexToID}{$AtomIndex2};
     558       if ( !((exists($This->{AssignedAtomTypes}{$AtomID2}) && @{$This->{AssignedAtomTypes}{$AtomID2}})) ) {
     559         next ATOMINDEX2;
     560       }
     561       $BinnedDistance12 = $BinnedDistanceMatrix->GetValue($AtomIndex1, $AtomIndex2, $SkipIndexCheck);
     562       if ($BinnedDistance12 == 0) {
     563         next ATOMINDEX2;
     564       }
     565
     566       ATOMINDEX3: for $AtomIndex3 (($AtomIndex2 + 1) .. ($NumOfAtoms - 1)) {
     567         $AtomID3 = $This->{AtomIndexToID}{$AtomIndex3};
     568         if ( !((exists($This->{AssignedAtomTypes}{$AtomID3}) && @{$This->{AssignedAtomTypes}{$AtomID3}})) ) {
     569           next ATOMINDEX3;
     570         }
     571         $BinnedDistance13 = $BinnedDistanceMatrix->GetValue($AtomIndex1, $AtomIndex3, $SkipIndexCheck);
     572         $BinnedDistance23 = $BinnedDistanceMatrix->GetValue($AtomIndex2, $AtomIndex3, $SkipIndexCheck);
     573         if ($BinnedDistance13 == 0 || $BinnedDistance23 == 0) {
     574           next ATOMINDEX3;
     575         }
     576         if ($This->{UseTriangleInequality} && !$This->_DoDistancesSatisfyTriangleInequality($BinnedDistance12, $BinnedDistance13, $BinnedDistance23)) {
     577           next ATOMINDEX3;
     578         }
     579
     580         # Go over possible pharmacohore triplets for the three pharmacophore atoms using the
     581         # binned distances...
     582         for $AtomType1 (@{$This->{AssignedAtomTypes}{$AtomID1}}) {
     583           for $AtomType2 (@{$This->{AssignedAtomTypes}{$AtomID2}}) {
     584             for $AtomType3 (@{$This->{AssignedAtomTypes}{$AtomID3}}) {
     585               $AtomTripletID = $This->_GetAtomTripletID($AtomType1, $BinnedDistance23, $AtomType2, $BinnedDistance13, $AtomType3, $BinnedDistance12);
     586               $This->{AtomTriplets}{Count}{$AtomTripletID} += 1;
     587             }
     588           }
     589         }
     590       }
     591     }
     592   }
     593   return $This;
     594 }
     595
     596 # Set final fingerpritns vector...
     597 #
     598 sub _SetFinalFingerprints {
     599   my($This) = @_;
     600   my($UseArbitrarySetSize, $ID, $Value, @IDs, @Values);
     601
     602   # Mark successful generation of fingerprints...
     603   $This->{FingerprintsGenerated} = 1;
     604
     605   # Is it an ArbitraySize atom triplets set size?
     606   $UseArbitrarySetSize = $This->{AtomTripletsSetSizeToUse} =~ /^ArbitrarySize$/i ? 1 : 0;
     607
     608   # Set atom triplet count values...
     609   @IDs = (); @Values = ();
     610
     611   if ($UseArbitrarySetSize) {
     612     ID: for $ID (@{$This->{AtomTriplets}{IDs}}) {
     613       $Value = $This->{AtomTriplets}{Count}{$ID};
     614       if ($Value == 0) {
     615         next ID;
     616       }
     617       push @IDs, $ID;
     618       push @Values, $Value;
     619     }
     620   }
     621   else {
     622     @Values = map { $This->{AtomTriplets}{Count}{$_} } @{$This->{AtomTriplets}{IDs}};
     623   }
     624
     625   # Set atom triplet IDs for fingerprint vector...
     626   if ($UseArbitrarySetSize) {
     627     $This->{FingerprintsVector}->AddValueIDs(\@IDs);
     628   }
     629   else {
     630     $This->{FingerprintsVector}->AddValueIDs(\@{$This->{AtomTriplets}{IDs}});
     631   }
     632
     633   # Set atom triplets count values for fingerprint vector...
     634   $This->{FingerprintsVector}->AddValues(\@Values);
     635
     636   return $This;
     637 }
     638
     639 # Return an array or reference to an array containing atom triplet IDs...
     640 #
     641 sub GetAtomTripletIDs {
     642   my($This) = @_;
     643
     644   return wantarray ? @{$This->{AtomTriplets}{IDs}} : \@{$This->{AtomTriplets}{IDs}};
     645 }
     646
     647 # Get pharmacophore atom triplet ID corresponding to atom types and distances
     648 # corresponding to atom triplet...
     649 #
     650 sub _GetAtomTripletID {
     651   my($This, $Px, $Dyz, $Py, $Dxz, $Pz, $Dxy) = @_;
     652   my($AtomTripletID, @AtomIDs);
     653
     654   @AtomIDs = ();
     655
     656   @AtomIDs = sort("${Px}${Dyz}", "${Py}${Dxz}", "${Pz}${Dxy}");
     657   $AtomTripletID = join "-", @AtomIDs;
     658
     659   return $AtomTripletID;
     660 }
     661
     662 # Cache  appropriate molecule data...
     663 #
     664 sub _SetupMoleculeDataCache {
     665   my($This) = @_;
     666
     667   # Get all atoms including hydrogens to correctly map atom indices to atom IDs for
     668   # usage of distance matrix. The hydrogen atoms are ignored during processing...
     669   #
     670   @{$This->{Atoms}} = $This->GetMolecule()->GetAtoms();
     671
     672   # Get all atom IDs...
     673   my(@AtomIDs);
     674   @AtomIDs = ();
     675   @AtomIDs =  map { $_->GetID() } @{$This->{Atoms}};
     676
     677   # Set AtomIndex to AtomID hash...
     678   %{$This->{AtomIndexToID}} = ();
     679   @{$This->{AtomIndexToID}}{ (0 .. $#AtomIDs) } = @AtomIDs;
     680
     681   return $This;
     682 }
     683
     684 # Clear cached molecule data...
     685 #
     686 sub _ClearMoleculeDataCache {
     687   my($This) = @_;
     688
     689   @{$This->{Atoms}} = ();
     690
     691   return $This;
     692 }
     693
     694
     695 # Return a string containg data for TopologicalPharmacophoreAtomTripletsFingerprints object...
     696 #
     697 sub StringifyTopologicalPharmacophoreAtomTripletsFingerprints {
     698   my($This) = @_;
     699   my($FingerprintsString, $UseTriangleInequality);
     700
     701   # Type of fingerprint...
     702   $FingerprintsString = "Fingerprint type: $This->{Type}; AtomTripletsSetSizeToUse: $This->{AtomTripletsSetSizeToUse}";
     703
     704   # Distances information...
     705   $FingerprintsString .= "; MinDistance:  $This->{MinDistance}; MaxDistance: $This->{MaxDistance}; DistanceBinSize: $This->{DistanceBinSize}; UseTriangleInequality: " . ($This->{UseTriangleInequality} ? "Yes" : "No");
     706
     707   # Pharmacophore atom type labels and description...
     708   my($AtomType, @AtomTypes, @AtomTypesOrder, %AvailableAtomTypes);
     709
     710   @AtomTypesOrder = AtomTypes::FunctionalClassAtomTypes::GetFunctionalClassesOrder();
     711   %AvailableAtomTypes = AtomTypes::FunctionalClassAtomTypes::GetAvailableFunctionalClasses();
     712
     713   @AtomTypes = ();
     714   for $AtomType (@AtomTypesOrder) {
     715     push @AtomTypes, "$AtomType: $AvailableAtomTypes{$AtomType}";
     716   }
     717
     718   $FingerprintsString .= "; AtomTypesToUse: <" . TextUtil::JoinWords(\@{$This->{AtomTypesToUse}}, ", ", 0) . ">";
     719   $FingerprintsString .= "; AtomTypesOrder: <" . TextUtil::JoinWords(\@AtomTypesOrder, ", ", 0) . ">";
     720   $FingerprintsString .= "; AvailableAtomTypes: <" . TextUtil::JoinWords(\@AtomTypes, ", ", 0) . ">";
     721
     722   # Total number of pharmacophore atom triplets...
     723   $FingerprintsString .= "; NumOfAtomTriplets: " . $This->{FingerprintsVector}->GetNumOfValues();
     724
     725   # FingerprintsVector...
     726   $FingerprintsString .= "; FingerprintsVector: < $This->{FingerprintsVector} >";
     727
     728   return $FingerprintsString;
     729 }
     730