MayaChemTools

   1 package Fingerprints::TopologicalAtomPairsFingerprints;
   2 #
   3 # $RCSfile: TopologicalAtomPairsFingerprints.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 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 '""' => 'StringifyTopologicalAtomPairsFingerprints';
  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->_InitializeTopologicalAtomPairsFingerprints();
  68 
  69   $This->_InitializeTopologicalAtomPairsFingerprintsProperties(%NamesAndValues);
  70 
  71   return $This;
  72 }
  73 
  74 # Initialize object data...
  75 #
  76 sub _InitializeTopologicalAtomPairsFingerprints {
  77   my($This) = @_;
  78 
  79   # Type of fingerprint...
  80   $This->{Type} = 'TopologicalAtomPairs';
  81 
  82   # Type of vector...
  83   $This->{VectorType} = 'FingerprintsVector';
  84 
  85   # Type of FingerprintsVector...
  86   $This->{FingerprintsVectorType} = 'NumericalValues';
  87 
  88   # Minimum and maximum bond distance between atom paris...
  89   $This->{MinDistance} = 1;
  90   $This->{MaxDistance} = 10;
  91 
  92   # Atom identifier type to use for atom IDs in atom pairs...
  93   #
  94   # Currently supported values are: AtomicInvariantsAtomTypes, DREIDINGAtomTypes,
  95   # EStateAtomTypes, FunctionalClassAtomTypes, MMFF94AtomTypes, SLogPAtomTypes,
  96   # SYBYLAtomTypes, TPSAAtomTypes, UFFAtomTypes
  97   #
  98   $This->{AtomIdentifierType} = '';
  99 
 100   # Atom types assigned to each heavy atom...
 101   #
 102   %{$This->{AssignedAtomTypes}} = ();
 103 
 104   # All atom pairs between minimum and maximum distance...
 105   #
 106   @{$This->{AtomPairsIDs}} = ();
 107   %{$This->{AtomPairsCount}} = ();
 108 }
 109 
 110 # Initialize class ...
 111 sub _InitializeClass {
 112   #Class name...
 113   $ClassName = __PACKAGE__;
 114 }
 115 
 116 # Initialize object properties....
 117 sub _InitializeTopologicalAtomPairsFingerprintsProperties {
 118   my($This, %NamesAndValues) = @_;
 119 
 120   my($Name, $Value, $MethodName);
 121   while (($Name, $Value) = each  %NamesAndValues) {
 122     $MethodName = "Set${Name}";
 123     $This->$MethodName($Value);
 124   }
 125 
 126   # Make sure molecule object was specified...
 127   if (!exists $NamesAndValues{Molecule}) {
 128     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying molecule...";
 129   }
 130   if (!exists $NamesAndValues{AtomIdentifierType}) {
 131     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying AtomIdentifierType...";
 132   }
 133 
 134   $This->_InitializeFingerprintsVector();
 135 
 136   return $This;
 137 }
 138 
 139 # Set minimum distance for atom pairs...
 140 #
 141 sub SetMinDistance {
 142   my($This, $Value) = @_;
 143 
 144   if (!TextUtil::IsPositiveInteger($Value)) {
 145     croak "Error: ${ClassName}->SetMinDistance: MinDistance value, $Value, is not valid:  It must be a positive integer...";
 146   }
 147   $This->{MinDistance} = $Value;
 148 
 149   return $This;
 150 }
 151 
 152 # Set maximum distance for atom pairs...
 153 #
 154 sub SetMaxDistance {
 155   my($This, $Value) = @_;
 156 
 157   if (!TextUtil::IsPositiveInteger($Value)) {
 158     croak "Error: ${ClassName}->SetMaxDistance: MaxDistance value, $Value, is not valid:  It must be a positive integer...";
 159   }
 160   $This->{MaxDistance} = $Value;
 161 
 162   return $This;
 163 }
 164 
 165 # Set atom identifier type..
 166 #
 167 sub SetAtomIdentifierType {
 168   my($This, $IdentifierType) = @_;
 169 
 170   if ($IdentifierType !~ /^(AtomicInvariantsAtomTypes|DREIDINGAtomTypes|EStateAtomTypes|FunctionalClassAtomTypes|MMFF94AtomTypes|SLogPAtomTypes|SYBYLAtomTypes|TPSAAtomTypes|UFFAtomTypes)$/i) {
 171     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.";
 172   }
 173 
 174   if ($This->{AtomIdentifierType}) {
 175     croak "Error: ${ClassName}->SeAtomIdentifierType: Can't change intial atom identifier type:  It's already set...";
 176   }
 177 
 178   $This->{AtomIdentifierType} = $IdentifierType;
 179 
 180   # Initialize atom identifier type information...
 181   $This->_InitializeAtomIdentifierTypeInformation();
 182 
 183   return $This;
 184 }
 185 
 186 # Generate fingerprints description...
 187 #
 188 sub GetDescription {
 189   my($This) = @_;
 190 
 191   # Is description explicity set?
 192   if (exists $This->{Description}) {
 193     return $This->{Description};
 194   }
 195 
 196   # Generate fingerprints description...
 197 
 198   return "$This->{Type}:$This->{AtomIdentifierType}:MinDistance$This->{MinDistance}:MaxDistance$This->{MaxDistance}";
 199 }
 200 
 201 # Generate topological atom pairs [ Ref 57, Ref 59, Ref 72 ] fingerprints...
 202 #
 203 # Methodology:
 204 #   . Generate a distance matrix.
 205 #   . Assign atom types to all the atoms.
 206 #   . Using distance matrix and atom types, count occurrence of
 207 #     unique atom pairs within specified distance range - It corresponds to the
 208 #     correlation-vector for the atom pairs.
 209 #
 210 # Notes:
 211 #   . Hydrogen atoms are ignored during the fingerprint generation.
 212 #
 213 sub GenerateFingerprints {
 214   my($This) = @_;
 215 
 216   if ($This->{MinDistance} > $This->{MaxDistance}) {
 217     croak "Error: ${ClassName}->GenerateTopologicalAtomPairsFingerprints: No fingerpritns generated: MinDistance, $This->{MinDistance}, must be <= MaxDistance, $This->{MaxDistance}...";
 218   }
 219 
 220   # Cache appropriate molecule data...
 221   $This->_SetupMoleculeDataCache();
 222 
 223   # Generate distance matrix...
 224   if (!$This->_SetupDistanceMatrix()) {
 225     carp "Warning: ${ClassName}->GenerateFingerprints: $This->{AtomIdentifierType} fingerprints generation didn't succeed: Couldn't generate distance matrix...";
 226     return $This;
 227   }
 228 
 229   # Assign atom types to all heavy atoms...
 230   if (!$This->_AssignAtomTypes()) {
 231     carp "Warning: ${ClassName}->GenerateFingerprints: $This->{AtomIdentifierType} fingerprints generation didn't succeed: Couldn't assign valid $This->{AtomIdentifierType} to all atoms...";
 232     return $This;
 233   }
 234 
 235   # Intialize values of toplogical atom pairs...
 236   $This->_InitializeToplogicalAtomPairs();
 237 
 238   # Count atom pairs...
 239   $This->_GenerateAndCountAtomPairs();
 240 
 241   # Set final fingerprints...
 242   $This->_SetFinalFingerprints();
 243 
 244   # Clear cached molecule data...
 245   $This->_ClearMoleculeDataCache();
 246 
 247   return $This;
 248 }
 249 
 250 # Setup distance matrix...
 251 #
 252 sub _SetupDistanceMatrix {
 253   my($This) = @_;
 254 
 255   $This->{DistanceMatrix} = $This->GetMolecule()->GetDistanceMatrix();
 256 
 257   if (!$This->{DistanceMatrix}) {
 258     return undef;
 259   }
 260 
 261   return $This;
 262 }
 263 
 264 # Assign appropriate atom types to all heavy atoms...
 265 #
 266 sub _AssignAtomTypes {
 267   my($This) = @_;
 268   my($SpecifiedAtomTypes, $Atom, $AtomID, $IgnoreHydrogens);
 269 
 270   %{$This->{AssignedAtomTypes}} = ();
 271   $IgnoreHydrogens = 1;
 272 
 273   $SpecifiedAtomTypes = undef;
 274 
 275   IDENTIFIERTYPE: {
 276     if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 277       $SpecifiedAtomTypes = new AtomTypes::AtomicInvariantsAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens, 'AtomicInvariantsToUse' => $This->{AtomicInvariantsToUse});
 278       last IDENTIFIERTYPE;
 279     }
 280 
 281     if ($This->{AtomIdentifierType} =~ /^DREIDINGAtomTypes$/i) {
 282       $SpecifiedAtomTypes = new AtomTypes::DREIDINGAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 283       last IDENTIFIERTYPE;
 284     }
 285 
 286     if ($This->{AtomIdentifierType} =~ /^EStateAtomTypes$/i) {
 287       $SpecifiedAtomTypes = new AtomTypes::EStateAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 288       last IDENTIFIERTYPE;
 289     }
 290 
 291     if ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 292       $SpecifiedAtomTypes = new AtomTypes::FunctionalClassAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens, 'FunctionalClassesToUse' => $This->{FunctionalClassesToUse});
 293       last IDENTIFIERTYPE;
 294     }
 295 
 296     if ($This->{AtomIdentifierType} =~ /^MMFF94AtomTypes$/i) {
 297       $SpecifiedAtomTypes = new AtomTypes::MMFF94AtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 298       last IDENTIFIERTYPE;
 299     }
 300 
 301     if ($This->{AtomIdentifierType} =~ /^SLogPAtomTypes$/i) {
 302       $SpecifiedAtomTypes = new AtomTypes::SLogPAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 303       last IDENTIFIERTYPE;
 304     }
 305     if ($This->{AtomIdentifierType} =~ /^SYBYLAtomTypes$/i) {
 306       $SpecifiedAtomTypes = new AtomTypes::SYBYLAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 307       last IDENTIFIERTYPE;
 308     }
 309 
 310     if ($This->{AtomIdentifierType} =~ /^TPSAAtomTypes$/i) {
 311       $SpecifiedAtomTypes = new AtomTypes::TPSAAtomTypes('Molecule' => $This->{Molecule}, 'IgnorePhosphorus' => 0, 'IgnoreSulfur' => 0);
 312       last IDENTIFIERTYPE;
 313     }
 314 
 315     if ($This->{AtomIdentifierType} =~ /^UFFAtomTypes$/i) {
 316       $SpecifiedAtomTypes = new AtomTypes::UFFAtomTypes('Molecule' => $This->{Molecule}, 'IgnoreHydrogens' => $IgnoreHydrogens);
 317       last IDENTIFIERTYPE;
 318     }
 319 
 320     croak "Error: ${ClassName}->_AssignAtomTypes: Unknown atom indentifier type $This->{AtomIdentifierType}...";
 321   }
 322 
 323   # Assign atom types...
 324   $SpecifiedAtomTypes->AssignAtomTypes();
 325 
 326   # Make sure atom types assignment is successful...
 327   if (!$SpecifiedAtomTypes->IsAtomTypesAssignmentSuccessful()) {
 328     return undef;
 329   }
 330 
 331   # Collect assigned atom types...
 332   ATOM: for $Atom (@{$This->{Atoms}}) {
 333     if ($Atom->IsHydrogen()) {
 334       next ATOM;
 335     }
 336     $AtomID = $Atom->GetID();
 337     $This->{AssignedAtomTypes}{$AtomID} = $SpecifiedAtomTypes->GetAtomType($Atom);
 338   }
 339 
 340   return $This;
 341 }
 342 
 343 # Initialize topological atom pairs between specified distance range...
 344 #
 345 sub _InitializeToplogicalAtomPairs {
 346   my($This) = @_;
 347   my($Distance);
 348 
 349   @{$This->{AtomPairsIDs}} = ();
 350   %{$This->{AtomPairsCount}} = ();
 351 
 352   for $Distance ($This->{MinDistance} .. $This->{MaxDistance}) {
 353     %{$This->{AtomPairsCount}{$Distance}} = ();
 354   }
 355 
 356   return $This;
 357 }
 358 
 359 # Count atom pairs between mininum and maximum distance at each
 360 # distance using distance matrix and atom types assiged to each heavy
 361 # atom.
 362 #
 363 # Notes:
 364 #   . The row and column indices of distance matrix correspond to atom indices.
 365 #   . Distance value of BigNumber implies the atom is not connected to any other atom.
 366 #   . Due to symmetric nature of distance matrix, only upper or lower triangular matrix
 367 #     needs to be processed during identification and count of atom pairs.
 368 #
 369 sub _GenerateAndCountAtomPairs {
 370   my($This) = @_;
 371 
 372   my($NumOfRows, $NumOfCols, $RowIndex, $ColIndex, $DistanceMatrix, $Distance, $AtomID1, $AtomID2, $AtomType1, $AtomType2, $SkipIndexCheck, $CountIncrement);
 373 
 374   $DistanceMatrix = $This->{DistanceMatrix};
 375   ($NumOfRows, $NumOfCols) = $DistanceMatrix->GetSize();
 376   $SkipIndexCheck = 0;
 377 
 378   ROWINDEX: for $RowIndex (0 .. ($NumOfRows - 1) ) {
 379     $AtomID1 = $This->{AtomIndexToID}{$RowIndex};
 380     if ( !(exists($This->{AssignedAtomTypes}{$AtomID1})) ) {
 381       next ROWINDEX;
 382     }
 383     $AtomType1 = $This->{AssignedAtomTypes}{$AtomID1};
 384 
 385     COLINDEX: for $ColIndex (($RowIndex + 1) .. ($NumOfCols - 1) ) {
 386       $AtomID2 = $This->{AtomIndexToID}{$ColIndex};
 387       if ( !(exists($This->{AssignedAtomTypes}{$AtomID2})) ) {
 388         next COLINDEX;
 389       }
 390       $Distance = $DistanceMatrix->GetValue($RowIndex, $ColIndex, $SkipIndexCheck);
 391       if ($Distance < $This->{MinDistance} || $Distance > $This->{MaxDistance}) {
 392         next COLINDEX;
 393       }
 394       $AtomType2 = $This->{AssignedAtomTypes}{$AtomID2};
 395 
 396       if ($AtomType1 le $AtomType2) {
 397         $This->_SetAtomPairsCount($Distance, $AtomType1, $AtomType2);
 398       }
 399       else {
 400         $This->_SetAtomPairsCount($Distance, $AtomType2, $AtomType1);
 401       }
 402     }
 403   }
 404   return $This;
 405 }
 406 
 407 # Set atom paris count for a specific atom ID pair at a specific distance...
 408 #
 409 sub _SetAtomPairsCount {
 410   my($This, $Distance, $AtomType1, $AtomType2) = @_;
 411 
 412   if (! exists $This->{AtomPairsCount}{$Distance}{$AtomType1}) {
 413     %{$This->{AtomPairsCount}{$Distance}{$AtomType1}} = ();
 414     $This->{AtomPairsCount}{$Distance}{$AtomType1}{$AtomType2} = 1;
 415     return $This;
 416   }
 417 
 418   if (exists $This->{AtomPairsCount}{$Distance}{$AtomType1}{$AtomType2}) {
 419     $This->{AtomPairsCount}{$Distance}{$AtomType1}{$AtomType2} += 1;
 420   }
 421   else {
 422     $This->{AtomPairsCount}{$Distance}{$AtomType1}{$AtomType2} = 1;
 423   }
 424 
 425   return $This;
 426 }
 427 
 428 # Set final fingerpritns vector...
 429 #
 430 sub _SetFinalFingerprints {
 431   my($This) = @_;
 432   my($Distance, $AtomType1, $AtomType2, $Value, @Values);
 433 
 434   # Mark successful generation of fingerprints...
 435   $This->{FingerprintsGenerated} = 1;
 436 
 437   @Values = ();
 438   @{$This->{AtomPairsIDs}} = ();
 439 
 440   for $Distance ($This->{MinDistance} .. $This->{MaxDistance}) {
 441     for $AtomType1 (sort keys %{$This->{AtomPairsCount}{$Distance}} ) {
 442       for $AtomType2 (sort keys %{$This->{AtomPairsCount}{$Distance}{$AtomType1}} ) {
 443         push @{$This->{AtomPairsIDs}}, "${AtomType1}-D${Distance}-${AtomType2}";
 444         $Value = $This->{AtomPairsCount}{$Distance}{$AtomType1}{$AtomType2};
 445         push @Values, $Value;
 446       }
 447     }
 448   }
 449 
 450   # Add AtomPairsIDs and values to fingerprint vector...
 451   $This->{FingerprintsVector}->AddValueIDs(\@{$This->{AtomPairsIDs}});
 452   $This->{FingerprintsVector}->AddValues(\@Values);
 453 
 454   return $This;
 455 }
 456 
 457 # Get atom pair IDs corresponding to atom pairs count values in fingerprint
 458 # vector as an array or reference to an array...
 459 #
 460 # AtomPairIDs list differes in molecules and is generated during finalization
 461 # of fingerprints to make sure the fingerprint vector containing count values
 462 # matches the atom pairs array.
 463 #
 464 sub GetAtomPairIDs {
 465   my($This) = @_;
 466 
 467   return wantarray ? @{$This->{AtomPairsIDs}} : \@{$This->{AtomPairsIDs}};
 468 }
 469 
 470 # Cache  appropriate molecule data...
 471 #
 472 sub _SetupMoleculeDataCache {
 473   my($This) = @_;
 474 
 475   # Get all atoms including hydrogens to correctly map atom indices to atom IDs for
 476   # usage of distance matrix. The hydrogen atoms are ignored during processing...
 477   #
 478   @{$This->{Atoms}} = $This->GetMolecule()->GetAtoms();
 479 
 480   # Get all atom IDs...
 481   my(@AtomIDs);
 482   @AtomIDs = ();
 483   @AtomIDs =  map { $_->GetID() } @{$This->{Atoms}};
 484 
 485   # Set AtomIndex to AtomID hash...
 486   %{$This->{AtomIndexToID}} = ();
 487   @{$This->{AtomIndexToID}}{ (0 .. $#AtomIDs) } = @AtomIDs;
 488 
 489   return $This;
 490 }
 491 
 492 # Clear cached molecule data...
 493 #
 494 sub _ClearMoleculeDataCache {
 495   my($This) = @_;
 496 
 497   @{$This->{Atoms}} = ();
 498 
 499   return $This;
 500 }
 501 
 502 # Set atomic invariants to use for atom identifiers...
 503 #
 504 sub SetAtomicInvariantsToUse {
 505   my($This, @Values) = @_;
 506   my($FirstValue, $TypeOfFirstValue, $AtomicInvariant, $SpecifiedAtomicInvariant, $AtomicInvariantValue, @SpecifiedAtomicInvariants, @AtomicInvariantsToUse);
 507 
 508   if (!@Values) {
 509     carp "Warning: ${ClassName}->SetAtomicInvariantsToUse: No values specified...";
 510     return;
 511   }
 512 
 513   $FirstValue = $Values[0];
 514   $TypeOfFirstValue = ref $FirstValue;
 515 
 516   @SpecifiedAtomicInvariants = ();
 517   @AtomicInvariantsToUse = ();
 518 
 519   if ($TypeOfFirstValue =~ /^ARRAY/) {
 520     push @SpecifiedAtomicInvariants, @{$FirstValue};
 521   }
 522   else {
 523     push @SpecifiedAtomicInvariants, @Values;
 524   }
 525 
 526   # Make sure specified AtomicInvariants are valid...
 527   for $SpecifiedAtomicInvariant (@SpecifiedAtomicInvariants) {
 528     if (!AtomTypes::AtomicInvariantsAtomTypes::IsAtomicInvariantAvailable($SpecifiedAtomicInvariant)) {
 529       croak "Error: ${ClassName}->SetAtomicInvariantsToUse: Specified atomic invariant, $SpecifiedAtomicInvariant, is not supported...\n ";
 530     }
 531     $AtomicInvariant = $SpecifiedAtomicInvariant;
 532     push @AtomicInvariantsToUse, $AtomicInvariant;
 533   }
 534 
 535   # Set atomic invariants to use...
 536   @{$This->{AtomicInvariantsToUse}} = ();
 537   push @{$This->{AtomicInvariantsToUse}}, @AtomicInvariantsToUse;
 538 
 539   return $This;
 540 }
 541 
 542 # Set functional classes to use for atom identifiers...
 543 #
 544 sub SetFunctionalClassesToUse {
 545   my($This, @Values) = @_;
 546   my($FirstValue, $TypeOfFirstValue, $FunctionalClass, $SpecifiedFunctionalClass, @SpecifiedFunctionalClasses, @FunctionalClassesToUse);
 547 
 548   if (!@Values) {
 549     carp "Warning: ${ClassName}->SetFunctionalClassesToUse: No values specified...";
 550     return;
 551   }
 552 
 553   if ($This->{AtomIdentifierType} !~ /^FunctionalClassAtomTypes$/i) {
 554     carp "Warning: ${ClassName}->SetFunctionalClassesToUse: FunctionalClassesToUse can't be set for InitialAtomIdentifierType of $This->{AtomIdentifierType}...";
 555     return;
 556   }
 557 
 558   $FirstValue = $Values[0];
 559   $TypeOfFirstValue = ref $FirstValue;
 560 
 561   @SpecifiedFunctionalClasses = ();
 562   @FunctionalClassesToUse = ();
 563 
 564   if ($TypeOfFirstValue =~ /^ARRAY/) {
 565     push @SpecifiedFunctionalClasses, @{$FirstValue};
 566   }
 567   else {
 568     push @SpecifiedFunctionalClasses, @Values;
 569   }
 570 
 571   # Make sure specified FunctionalClasses are valid...
 572   for $SpecifiedFunctionalClass (@SpecifiedFunctionalClasses) {
 573     if (!AtomTypes::FunctionalClassAtomTypes::IsFunctionalClassAvailable($SpecifiedFunctionalClass)) {
 574       croak "Error: ${ClassName}->SetFunctionalClassesToUse: Specified functional class, $SpecifiedFunctionalClass, is not supported...\n ";
 575     }
 576     push @FunctionalClassesToUse, $SpecifiedFunctionalClass;
 577   }
 578 
 579   # Set functional classes to use...
 580   @{$This->{FunctionalClassesToUse}} = ();
 581   push @{$This->{FunctionalClassesToUse}}, @FunctionalClassesToUse;
 582 
 583   return $This;
 584 }
 585 
 586 # Initialize atom indentifier type information...
 587 #
 588 # Current supported values:
 589 #
 590 # AtomicInvariantsAtomTypes, DREIDINGAtomTypes, EStateAtomTypes, FunctionalClassAtomTypes,
 591 # MMFF94AtomTypes, SLogPAtomTypes, SYBYLAtomTypes, TPSAAtomTypes, UFFAtomTypes
 592 #
 593 sub _InitializeAtomIdentifierTypeInformation {
 594   my($This) = @_;
 595 
 596   if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 597     $This->_InitializeAtomicInvariantsAtomTypesInformation();
 598   }
 599   elsif ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 600     $This->_InitializeFunctionalClassAtomTypesInformation();
 601   }
 602   elsif ($This->{AtomIdentifierType} =~ /^(DREIDINGAtomTypes|EStateAtomTypes|MMFF94AtomTypes|SLogPAtomTypes|SYBYLAtomTypes|TPSAAtomTypes|UFFAtomTypes)$/i) {
 603     # Nothing to do for now...
 604   }
 605   else {
 606     croak "Error: ${ClassName}->_InitializeAtomIdentifierTypeInformation: Unknown atom indentifier type $This->{AtomIdentifierType}...";
 607   }
 608 
 609   return $This;
 610 }
 611 
 612 # Initialize atomic invariants atom types to use for generating atom identifiers...
 613 #
 614 # Let:
 615 #   AS = Atom symbol corresponding to element symbol
 616 #
 617 #   X<n>   = Number of non-hydrogen atom neighbors or heavy atoms attached to atom
 618 #   BO<n> = Sum of bond orders to non-hydrogen atom neighbors or heavy atoms attached to atom
 619 #   LBO<n> = Largest bond order of non-hydrogen atom neighbors or heavy atoms attached to atom
 620 #   SB<n> = Number of single bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 621 #   DB<n> = Number of double bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 622 #   TB<n> = Number of triple bonds to non-hydrogen atom neighbors or heavy atoms attached to atom
 623 #   H<n>   = Number of implicit and explicit hydrogens for atom
 624 #   Ar     = Aromatic annotation indicating whether atom is aromatic
 625 #   RA     = Ring atom annotation indicating whether atom is a ring
 626 #   FC<+n/-n> = Formal charge assigned to atom
 627 #   MN<n> = Mass number indicating isotope other than most abundant isotope
 628 #   SM<n> = Spin multiplicity of atom. Possible values: 1 (singlet), 2 (doublet) or 3 (triplet)
 629 #
 630 #   AtomTypeIDx = Atomic invariants atom type for atom x
 631 #   AtomTypeIDy = Atomic invariants atom type for atom y
 632 #   Dn   = Topological distance between atom x and y
 633 #
 634 # Then:
 635 #
 636 #   Atom pair AtomID generated by AtomTypes::AtomicInvariantsAtomTypes class corresponds to:
 637 #
 638 #     AS.X<n>.BO<n>.LBO<n>.<SB><n>.<DB><n>.<TB><n>.H<n>.Ar.RA.FC<+n/-n>.MN<n>.SM<n>
 639 #
 640 #  AtomPairID corresponds to:
 641 #
 642 #    AtomTypeIDx-D<n>-AtomTypeIDy
 643 #
 644 # Except for AS which is a required atomic invariant in atom pair AtomIDs, all other atomic invariants are
 645 # optional. Default atomic invariants used for AtomID are: AS, X<n>, BO<n>, H<n>, FC<+n/-n>.
 646 # AtomID specification doesn't include atomic invariants with zero or undefined values.
 647 #
 648 # Examples of atom pair AtomIDs:
 649 #
 650 #   O.X1.BO1.H1 - Hydroxyl oxygen in carboxylate with attached hydrogen and no explicit charge
 651 #   O.X1.BO1.FC-1 - Hydroxyl ozygen in carboxylate with explicit negative charge
 652 #   O.X1.BO2 - Carbonyl oxygen in carboxylate with double bond to carbon
 653 #   O.X2.BO2 - Hydroxyl ozygen in carboxylate attached to carbonyl carbon and another heavy atom
 654 #
 655 #   C.X2.BO3.H1.Ar - Aromatic carbon
 656 #
 657 # Examples of AtomPairIDs:
 658 #
 659 #   C.X2.BO2.H3-D1-O.X1.BO1 - Carbon with two heavy atom neighbors attached to oxygen at bond distance 1(methanol)
 660 #
 661 #   C.X2.BO3.H1.Ar-D3-C.X2.BO3.H1.Ar  - Two aromatic carbons at bond distance 3 where each carbon has
 662 #                                       two heavy atom neighbors and bond order of 3 (benzene)
 663 #
 664 sub _InitializeAtomicInvariantsAtomTypesInformation {
 665   my($This) = @_;
 666 
 667   # Default atomic invariants to use for generating atom neighborhood atom IDs: AS, X, BO, H, FC
 668   #
 669   @{$This->{AtomicInvariantsToUse}} = ();
 670   @{$This->{AtomicInvariantsToUse}} = ('AS', 'X', 'BO', 'H', 'FC');
 671 
 672   return $This;
 673 }
 674 
 675 # Initialize functional class atom types, generated by AtomTypes::FunctionalClassAtomTypes
 676 # class, to use for generating atom identifiers...
 677 #
 678 # Let:
 679 #   HBD: HydrogenBondDonor
 680 #   HBA: HydrogenBondAcceptor
 681 #   PI :  PositivelyIonizable
 682 #   NI : NegativelyIonizable
 683 #   Ar : Aromatic
 684 #   Hal : Halogen
 685 #   H : Hydrophobic
 686 #   RA : RingAtom
 687 #   CA : ChainAtom
 688 #
 689 # Then:
 690 #
 691 #   Functiononal class atom type specification for an atom corresponds to:
 692 #
 693 #     Ar.CA.H.HBA.HBD.Hal.NI.PI.RA
 694 #
 695 #   Default functional classes used are: HBD, HBA, PI, NI, Ar, Hal
 696 #
 697 #   FunctionalAtomTypes are assigned using the following definitions [ Ref 60-61, Ref 65-66 ]:
 698 #
 699 #     HydrogenBondDonor: NH, NH2, OH
 700 #     HydrogenBondAcceptor: N[!H], O
 701 #     PositivelyIonizable: +, NH2
 702 #     NegativelyIonizable: -, C(=O)OH, S(=O)OH, P(=O)OH
 703 #
 704 sub _InitializeFunctionalClassAtomTypesInformation {
 705   my($This) = @_;
 706 
 707   # Default functional class atom typess to use for generating atom identifiers
 708   # are: HBD, HBA, PI, NI, Ar, Hal
 709   #
 710   @{$This->{FunctionalClassesToUse}} = ();
 711   @{$This->{FunctionalClassesToUse}} = ('HBD', 'HBA', 'PI', 'NI', 'Ar', 'Hal');
 712 
 713   return $This;
 714 }
 715 
 716 # Return a string containg data for TopologicalAtomPairsFingerprints object...
 717 #
 718 sub StringifyTopologicalAtomPairsFingerprints {
 719   my($This) = @_;
 720   my($FingerprintsString);
 721 
 722   # Type of fingerprint...
 723   $FingerprintsString = "Fingerprint type: $This->{Type}; AtomIdentifierType: $This->{AtomIdentifierType}";
 724 
 725   # Min and max distance...
 726   $FingerprintsString .= "; MinDistance:  $This->{MinDistance}; MaxDistance: $This->{MaxDistance}";
 727 
 728   if ($This->{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
 729     my($AtomicInvariant, @AtomicInvariants, @AtomicInvariantsOrder, %AvailableAtomicInvariants);
 730 
 731     @AtomicInvariantsOrder = AtomTypes::AtomicInvariantsAtomTypes::GetAtomicInvariantsOrder();
 732     %AvailableAtomicInvariants = AtomTypes::AtomicInvariantsAtomTypes::GetAvailableAtomicInvariants();
 733 
 734     for $AtomicInvariant (@AtomicInvariantsOrder) {
 735       push @AtomicInvariants, "$AtomicInvariant: $AvailableAtomicInvariants{$AtomicInvariant}";
 736     }
 737 
 738     $FingerprintsString .= "; AtomicInvariantsToUse: <" . TextUtil::JoinWords(\@{$This->{AtomicInvariantsToUse}}, ", ", 0) . ">";
 739     $FingerprintsString .= "; AtomicInvariantsOrder: <" . TextUtil::JoinWords(\@AtomicInvariantsOrder, ", ", 0) . ">";
 740     $FingerprintsString .= "; AvailableAtomicInvariants: <" . TextUtil::JoinWords(\@AtomicInvariants, ", ", 0) . ">";
 741   }
 742   elsif ($This->{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
 743     my($FunctionalClass, @FunctionalClasses, @FunctionalClassesOrder, %AvailableFunctionalClasses);
 744 
 745     @FunctionalClassesOrder = AtomTypes::FunctionalClassAtomTypes::GetFunctionalClassesOrder();
 746     %AvailableFunctionalClasses = AtomTypes::FunctionalClassAtomTypes::GetAvailableFunctionalClasses();
 747 
 748     for $FunctionalClass (@FunctionalClassesOrder) {
 749       push @FunctionalClasses, "$FunctionalClass: $AvailableFunctionalClasses{$FunctionalClass}";
 750     }
 751 
 752     $FingerprintsString .= "; FunctionalClassesToUse: <" . TextUtil::JoinWords(\@{$This->{FunctionalClassesToUse}}, ", ", 0) . ">";
 753     $FingerprintsString .= "; FunctionalClassesOrder: <" . TextUtil::JoinWords(\@FunctionalClassesOrder, ", ", 0) . ">";
 754     $FingerprintsString .= "; AvailableFunctionalClasses: <" . TextUtil::JoinWords(\@FunctionalClasses, ", ", 0) . ">";
 755   }
 756 
 757   # Total number of atom pairs...
 758   $FingerprintsString .= "; NumOfAtomPairs: " . $This->{FingerprintsVector}->GetNumOfValues();
 759 
 760   # FingerprintsVector...
 761   $FingerprintsString .= "; FingerprintsVector: < $This->{FingerprintsVector} >";
 762 
 763   return $FingerprintsString;
 764 }
 765