MayaChemTools

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