MayaChemTools

   1 package Fingerprints::TopologicalPharmacophoreAtomTripletsFingerprints;
   2 #
   3 # File: TopologicalPharmacophoreAtomTripletsFingerprints.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 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