MayaChemTools

   1 package MolecularDescriptors::MolecularDescriptorsGenerator;
   2 #
   3 # File: MolecularDescriptorsGenerator.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 Scalar::Util ();
  30 use ObjectProperty;
  31 use TextUtil ();
  32 use FileUtil ();
  33 use Molecule;
  34 use MolecularDescriptors::MolecularDescriptors;
  35 
  36 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  37 
  38 @ISA = qw(ObjectProperty Exporter);
  39 @EXPORT = qw();
  40 @EXPORT_OK = qw(GetAvailableDescriptorClassNames GetAvailableClassAndDescriptorNames GetAvailableDescriptorNames GetAvailableDescriptorNamesForDescriptorClass GetAvailableClassNameForDescriptorName GetRuleOf5DescriptorNames GetRuleOf3DescriptorNames IsDescriptorClassNameAvailable IsDescriptorNameAvailable);
  41 
  42 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  43 
  44 # Setup class variables...
  45 my($ClassName, %DescriptorsDataMap);
  46 _InitializeClass();
  47 
  48 # Overload Perl functions...
  49 use overload '""' => 'StringifyMolecularDescriptorsGenerator';
  50 
  51 # Class constructor...
  52 sub new {
  53   my($Class, %NamesAndValues) = @_;
  54 
  55   # Initialize object...
  56   my $This = {};
  57   bless $This, ref($Class) || $Class;
  58   $This->_InitializeMolecularDescriptorsGenerator();
  59 
  60   $This->_InitializeMolecularDescriptorsGeneratorProperties(%NamesAndValues);
  61 
  62   return $This;
  63 }
  64 
  65 # Initialize class ...
  66 sub _InitializeClass {
  67   #Class name...
  68   $ClassName = __PACKAGE__;
  69 
  70   # Load available molecular descriptor classes...
  71   _LoadMolecularDescriptorsData();
  72 
  73 }
  74 
  75 # Initialize object data...
  76 #
  77 sub _InitializeMolecularDescriptorsGenerator {
  78   my($This) = @_;
  79 
  80   # Type of desciptors to generate...
  81   #
  82   # The current release of MayaChemTools supports generation of four sets of
  83   # descriptors: All available descriptors, rule of 5 or 3 descriptors or a specified
  84   # set of descriptors.
  85   #
  86   # Possible values: All, RuleOf5, RuleOf3 or Specify
  87   #
  88   # RuleOf5 [ Ref 91 ] descriptor names: MolecularWeight, HydrogenBondDonors, HydrogenBondAcceptors,
  89   # SLogP. RuleOf5 states: MolecularWeight <= 500, HydrogenBondDonors <= 5, HydrogenBondAcceptors <= 10,
  90   # and logP <= 5.
  91   #
  92   # RuleOf3 [ Ref 92 ] descriptor names: MolecularWeight, RotatableBonds, HydrogenBondDonors,
  93   # HydrogenBondAcceptors, SLogP, TPSA. RuleOf3 states: MolecularWeight <= 300, RotatableBonds <= 3,
  94   # HydrogenBondDonors <= 3, HydrogenBondAcceptors <= 3, logP <= 3, and TPSA <= 60.
  95   #
  96   # For Specify value of Mode, a set of descritor names must be specified using
  97   # DescriptorNames parameter.
  98   #
  99   # Default: All
 100   #
 101   $This->{Mode} = '';
 102 
 103   # Descriptor names used to generate descriptor values during a specified descriptor
 104   # generation mode...
 105   #
 106   @{$This->{DescriptorNames}} = ();
 107 
 108   # Descriptor calculation control parameters for specified descriptor class names...
 109   #
 110   # These parameters are passed on to appropriate descriptor classes during
 111   # instantiations of descriptor class objects.
 112   #
 113   %{$This->{DescriptorClassParameters}} = ();
 114 
 115   $This->{DescriptorClassesInstantiated} = 0;
 116 
 117   # Descriptor class names and objects corresponding to specified descriptor names...
 118   #
 119   @{$This->{DescriptorClassNames}} = ();
 120   %{$This->{DescriptorClassObjects}} = ();
 121 
 122   # Descriptor values generated for specified descriptor names...
 123   #
 124   @{$This->{DescriptorValues}} = ();
 125 
 126   return $This;
 127 }
 128 
 129 # Initialize object properties...
 130 #
 131 sub _InitializeMolecularDescriptorsGeneratorProperties {
 132   my($This, %NamesAndValues) = @_;
 133 
 134   my($Name, $Value, $MethodName);
 135   while (($Name, $Value) = each  %NamesAndValues) {
 136     $MethodName = "Set${Name}";
 137     $This->$MethodName($Value);
 138   }
 139 
 140   # Set default value for Mode...
 141   if (!$This->{Mode}) {
 142     $This->{Mode} = 'All';
 143   }
 144 
 145   $This->_CheckAndInitializeDescriptorNames();
 146 
 147   return $This;
 148 }
 149 
 150 # Set descriptors generation mode......
 151 #
 152 sub SetMode {
 153   my($This, $Value) = @_;
 154 
 155   # All - all available descriptors
 156   # Specify - Specified set of descriptors
 157 
 158   if ($Value !~ /^(All|RuleOf5|RuleOf3|Specify)$/i) {
 159     croak "Error: ${ClassName}->SetMode: Mode value, $Value, is not valid; Supported values: All, RuleOf5, RuleOf3 or Specify...";
 160   }
 161 
 162   $This->{Mode} = $Value;
 163 
 164   return $This;
 165 }
 166 
 167 # Set descriptor names to use for generating descriptor values using an array
 168 # or reference to an array...
 169 #
 170 sub SetDescriptorNames {
 171   my($This, @Values) = @_;
 172 
 173   if ($This->{Mode} =~ /^All$/i) {
 174     croak "Error: ${ClassName}->SetDescriptorNames: Descriptor names cann't be specified during \"All\" value of descsriptors generation \"Mode\"...";
 175   }
 176 
 177   if (!@Values) {
 178     return;
 179   }
 180 
 181   my($FirstValue, $TypeOfFirstValue);
 182 
 183   $FirstValue = $Values[0];
 184   $TypeOfFirstValue = ref $FirstValue;
 185 
 186   @{$This->{DescriptorNames}} = ();
 187 
 188   if ($TypeOfFirstValue =~ /^ARRAY/) {
 189     # Initialize using array refernce...
 190     push @{$This->{DescriptorNames}}, @{$FirstValue};
 191   }
 192   else {
 193     # It's a list of values...
 194     push @{$This->{DescriptorNames}}, @Values;
 195   }
 196 
 197   # Make sure specified descriptor names are valid...
 198   $This->_ValidateDescriptorNames();
 199 
 200   return $This;
 201 }
 202 
 203 # Get descriptor names as an array...
 204 #
 205 sub GetDescriptorNames {
 206   my($This) = @_;
 207 
 208   return wantarray ? @{$This->{DescriptorNames}} : scalar @{$This->{DescriptorNames}};
 209 }
 210 
 211 # Get all descriptor values as an array...
 212 #
 213 sub GetDescriptorValues {
 214   my($This) = @_;
 215 
 216   if ($This->{DescriptorsGenerated}) {
 217     return wantarray ? @{$This->{DescriptorValues}} : scalar @{$This->{DescriptorValues}};
 218   }
 219   else {
 220     my(@DescriptorValues);
 221 
 222     @DescriptorValues = ('None') x scalar @{$This->{DescriptorNames}};
 223 
 224     return wantarray ? @DescriptorValues : scalar @DescriptorValues;
 225   }
 226 }
 227 
 228 # Get descriptor value for a specified descriptor name...
 229 #
 230 sub GetDescriptorValueByName {
 231   my($This, $Name) = @_;
 232   my(%NamesAndValues);
 233 
 234   %NamesAndValues = $This->GetDescriptorNamesAndValues();
 235 
 236   return exists $NamesAndValues{$Name} ? $NamesAndValues{$Name} : 'None';
 237 
 238 }
 239 
 240 # Get calculated molecular descriptor names sand values as a  hash with names
 241 # and values as key/value pairs...
 242 #
 243 sub GetDescriptorNamesAndValues {
 244   my($This) = @_;
 245   my(%NamesAndValues);
 246 
 247   %NamesAndValues = ();
 248   @NamesAndValues{ @{$This->{DescriptorNames}} } = $This->GetDescriptorValues();
 249 
 250   return %NamesAndValues;
 251 }
 252 
 253 # Set up descriptor calculation control parameters for a specified descriptor class name...
 254 #
 255 # The specified parameter names and values are simply passed on to specified descriptor
 256 # class during instantiation of descriptor class object without any performing any validation
 257 # of parameter names and associated values. It's up to the appropriate descriptor class methods
 258 # to validate these parameters and values.
 259 #
 260 # In addition to specified parameter names and values, the parameter hash must also contain
 261 # descriptor class name as key and value pair with DescriptorClassName as key with class
 262 # name as value.
 263 #
 264 sub SetDescriptorClassParameters {
 265   my($This, %NamesAndValues) = @_;
 266   my($DescriptorClassName, $Name, $Value);
 267 
 268   if (!exists $NamesAndValues{DescriptorClassName}) {
 269     croak "Error: ${ClassName}->_SetDescriptorNameParameters: Can't set descriptor class name paramaters: DescriptorClassName is not specified...";
 270   }
 271 
 272   $DescriptorClassName = $NamesAndValues{DescriptorClassName};
 273   if (!IsDescriptorClassNameAvailable($DescriptorClassName)) {
 274     carp "Warning: ${ClassName}->_SetDescriptorClassParameters: Can't set descriptor class name paramaters: Specified descriptor class name, $DescriptorClassName, is not available...";
 275     return $This;
 276   }
 277 
 278   if (exists $This->{DescriptorClassParameters}{$DescriptorClassName}) {
 279     carp "Warning: ${ClassName}->SetDescriptorClassParameters: Class name parameters for $DescriptorClassName have already been specified: Replacing previous values...";
 280   }
 281 
 282   %{$This->{DescriptorClassParameters}{$DescriptorClassName}} = ();
 283   NAME: while (($Name, $Value) = each  %NamesAndValues) {
 284     if ($Name =~ /^DescriptorClassName$/) {
 285       next NAME;
 286     }
 287     $This->{DescriptorClassParameters}{$DescriptorClassName}{$Name} = $Value;
 288   }
 289 
 290   return $This;
 291 }
 292 
 293 # Get descriptor name parameters as a reference to hash of hashes with hash
 294 # keys corresponding to class name and class parameter name with hash value
 295 # as class parameter value...
 296 #
 297 sub GetDescriptorClassParameters {
 298   my($This) = @_;
 299 
 300   return \%{$This->{DescriptorClassParameters}};
 301 }
 302 
 303 # Get available descriptor class names as an array.
 304 #
 305 # This functionality can be either invoked as a class function or an
 306 # object method.
 307 #
 308 sub GetAvailableDescriptorClassNames {
 309 
 310   return wantarray ? @{$DescriptorsDataMap{ClassNames}} : scalar @{$DescriptorsDataMap{ClassNames}};
 311 }
 312 
 313 # Get available descriptors class and descriptors names as a hash containing key/value
 314 # pairs corresponding to class name and an array of descriptor names available for the
 315 # class.
 316 #
 317 # This functionality can be either invoked as a class function or an
 318 # object method.
 319 #
 320 sub GetAvailableClassAndDescriptorNames {
 321   my($DescriptorClassName, @DescriptorNames, %ClassAndDescriptorNames);
 322 
 323   %ClassAndDescriptorNames = ();
 324   for $DescriptorClassName (@{$DescriptorsDataMap{ClassNames}}) {
 325     @{$ClassAndDescriptorNames{$DescriptorClassName}} = ();
 326     push @{$ClassAndDescriptorNames{$DescriptorClassName}}, @{$DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}};
 327   }
 328 
 329   return %ClassAndDescriptorNames;
 330 }
 331 
 332 # Get available descriptor names as an array.
 333 #
 334 # This functionality can be either invoked as a class function or an
 335 # object method.
 336 #
 337 sub GetAvailableDescriptorNames {
 338   my(@DescriptorNames);
 339 
 340   @DescriptorNames = ();
 341   push @DescriptorNames, map { @{$DescriptorsDataMap{ClassToDescriptorNames}{$_}} } @{$DescriptorsDataMap{ClassNames}};
 342 
 343   return wantarray ? @DescriptorNames : scalar @DescriptorNames;
 344 }
 345 
 346 # Is it a valid descriptors class name?
 347 #
 348 # This functionality can be either invoked as a class function or an
 349 # object method.
 350 #
 351 sub IsDescriptorClassNameAvailable {
 352   my($FirstParameter, $SecondParameter) = @_;
 353   my($This, $DescriptorClassName);
 354 
 355   if ((@_ == 2) && (_IsMolecularDescriptorsGenerator($FirstParameter))) {
 356     ($This, $DescriptorClassName) = ($FirstParameter, $SecondParameter);
 357   }
 358   else {
 359     ($DescriptorClassName) = ($FirstParameter);
 360   }
 361 
 362   return (exists $DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}) ? 1 : 0;
 363 }
 364 
 365 # Is it a valid descriptor name?
 366 #
 367 # This functionality can be either invoked as a class function or an
 368 # object method.
 369 #
 370 sub IsDescriptorNameAvailable {
 371   my($FirstParameter, $SecondParameter) = @_;
 372   my($This, $DescriptorName);
 373 
 374   if ((@_ == 2) && (_IsMolecularDescriptorsGenerator($FirstParameter))) {
 375     ($This, $DescriptorName) = ($FirstParameter, $SecondParameter);
 376   }
 377   else {
 378     ($DescriptorName) = ($FirstParameter);
 379   }
 380 
 381   return (exists $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName}) ? 1 : 0;
 382 }
 383 
 384 # Get available descriptors names for a descriptor class as an array.
 385 #
 386 # This functionality can be either invoked as a class function or an
 387 # object method.
 388 #
 389 sub GetAvailableDescriptorNamesForDescriptorClass {
 390   my($FirstParameter, $SecondParameter) = @_;
 391   my($This, $DescriptorClassName, @DescriptorNames);
 392 
 393   if ((@_ == 2) && (_IsMolecularDescriptorsGenerator($FirstParameter))) {
 394     ($This, $DescriptorClassName) = ($FirstParameter, $SecondParameter);
 395   }
 396   else {
 397     ($DescriptorClassName) = ($FirstParameter);
 398   }
 399 
 400   @DescriptorNames = ();
 401   if (exists $DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}) {
 402     push @DescriptorNames, @{$DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}};
 403   }
 404 
 405   return wantarray ? @DescriptorNames : scalar @DescriptorNames;
 406 }
 407 
 408 # Get available descriptors class name for a descriptor name.
 409 #
 410 # This functionality can be either invoked as a class function or an
 411 # object method.
 412 #
 413 sub GetAvailableClassNameForDescriptorName {
 414   my($FirstParameter, $SecondParameter) = @_;
 415   my($This, $DescriptorClassName, $DescriptorName);
 416 
 417   if ((@_ == 2) && (_IsMolecularDescriptorsGenerator($FirstParameter))) {
 418     ($This, $DescriptorName) = ($FirstParameter, $SecondParameter);
 419   }
 420   else {
 421     ($DescriptorName) = ($FirstParameter);
 422   }
 423 
 424   $DescriptorClassName = '';
 425   if (exists $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName}) {
 426     $DescriptorClassName = $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName};
 427   }
 428 
 429   return $DescriptorClassName;
 430 }
 431 
 432 # Get RuleOf5 descriptor names as an array.
 433 #
 434 # This functionality can be either invoked as a class function or an
 435 # object method.
 436 #
 437 sub GetRuleOf5DescriptorNames {
 438   my(@DescriptorNames);
 439 
 440   @DescriptorNames = qw(MolecularWeight HydrogenBondDonors HydrogenBondAcceptors SLogP);
 441 
 442   return wantarray ? @DescriptorNames : scalar @DescriptorNames;
 443 }
 444 
 445 # Get RuleOf3 descriptor names as an array.
 446 #
 447 # This functionality can be either invoked as a class function or an
 448 # object method.
 449 #
 450 sub GetRuleOf3DescriptorNames {
 451   my(@DescriptorNames);
 452 
 453   @DescriptorNames = qw(MolecularWeight RotatableBonds HydrogenBondDonors HydrogenBondAcceptors SLogP TPSA);
 454 
 455   return wantarray ? @DescriptorNames : scalar @DescriptorNames;
 456 }
 457 
 458 
 459 # Set molecule object...
 460 #
 461 sub SetMolecule {
 462   my($This, $Molecule) = @_;
 463 
 464   $This->{Molecule} = $Molecule;
 465 
 466   # Weaken the reference to disable increment of reference count...
 467   Scalar::Util::weaken($This->{Molecule});
 468 
 469   return $This;
 470 }
 471 
 472 # Generate specified molecular descriptors...
 473 #
 474 # After instantiating descriptor class objects at first invocation and  initialializing
 475 # descriptor values during subsequent invocations, GenerateDescriptors method
 476 # provided by each descriptor class is used to calculate descriptor values for
 477 # specified descriptors.
 478 #
 479 sub GenerateDescriptors {
 480   my($This) = @_;
 481   my($DescriptorClassName, $DescriptorClassObject);
 482 
 483   # Initialize descriptor values...
 484   $This->_InitializeDescriptorValues();
 485 
 486   # Instantiate decriptor classed corresponding to specified descriptors...
 487   if (!$This->{DescriptorClassesInstantiated}) {
 488     $This->_InstantiateDescriptorClasses();
 489   }
 490 
 491   # Check availability of molecule...
 492   if (!$This->{Molecule}) {
 493     carp "Warning: ${ClassName}->GenerateDescriptors: $This->{Type} molecular descriptors generation didn't succeed: Molecule data is not available: Molecule object hasn't been set...";
 494     return undef;
 495   }
 496 
 497   # Calculate descriptor values...
 498   for $DescriptorClassName (@{$This->{DescriptorClassNames}}) {
 499     $DescriptorClassObject = $This->{DescriptorClassObjects}{$DescriptorClassName};
 500 
 501     $DescriptorClassObject->SetMolecule($This->{Molecule});
 502     $DescriptorClassObject->GenerateDescriptors();
 503 
 504     if (!$DescriptorClassObject->IsDescriptorsGenerationSuccessful()) {
 505       return undef;
 506     }
 507   }
 508 
 509   # Set final descriptor values...
 510   $This->_SetFinalDescriptorValues();
 511 
 512   return $This;
 513 }
 514 
 515 # Initialize descriptor values...
 516 #
 517 sub _InitializeDescriptorValues {
 518   my($This) = @_;
 519 
 520   $This->{DescriptorsGenerated} = 0;
 521 
 522   @{$This->{DescriptorValues}} = ();
 523 
 524   return $This;
 525 }
 526 
 527 # Setup final descriptor values...
 528 #
 529 sub _SetFinalDescriptorValues {
 530   my($This) = @_;
 531   my($DescriptorName, $DescriptorClassName, $DescriptorClassObject);
 532 
 533   $This->{DescriptorsGenerated} = 1;
 534 
 535   @{$This->{DescriptorValues}} = ();
 536 
 537   if ($This->{Mode} =~ /^All$/i) {
 538     # Set descriptor values for all available descriptors...
 539     for $DescriptorClassName (@{$This->{DescriptorClassNames}}) {
 540       $DescriptorClassObject = $This->{DescriptorClassObjects}{$DescriptorClassName};
 541 
 542       push @{$This->{DescriptorValues}}, $DescriptorClassObject->GetDescriptorValues();
 543     }
 544   }
 545   else {
 546     # Set descriptor values for a subset of available descriptors...
 547     for $DescriptorName (@{$This->{DescriptorNames}}) {
 548       $DescriptorClassName = $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName};
 549       $DescriptorClassObject = $This->{DescriptorClassObjects}{$DescriptorClassName};
 550 
 551       push @{$This->{DescriptorValues}}, $DescriptorClassObject->GetDescriptorValueByName($DescriptorName);
 552     }
 553   }
 554 
 555   return $This;
 556 }
 557 
 558 # Is descriptors generation successful?
 559 #
 560 # Notes:
 561 #   . After successful generation of descriptor values by each descriptor class
 562 #     corresponding to specified descriptor names, DescriptorsCalculated  to 1;
 563 #     otherwise, it's set to 0.
 564 #
 565 sub IsDescriptorsGenerationSuccessful {
 566   my($This) = @_;
 567 
 568   return $This->{DescriptorsGenerated} ? 1 : 0;
 569 }
 570 
 571 # Check and set default descriptor names for generating descriptor values...
 572 #
 573 sub _CheckAndInitializeDescriptorNames {
 574   my($This) = @_;
 575 
 576   if ($This->{Mode} =~ /^(All|RuleOf5|RuleOf3)$/i) {
 577     if (@{$This->{DescriptorNames}}) {
 578       croak "Error: ${ClassName}->_CheckAndInitializeDescriptorNames: Descriptor names can't be specified during \"All, RuleOf5 or RuleOf3\" values of descsriptors generation \"Mode\"...";
 579     }
 580   }
 581 
 582   if ($This->{Mode} =~ /^All$/i) {
 583     @{$This->{DescriptorNames}} = GetAvailableDescriptorNames();
 584   }
 585   elsif ($This->{Mode} =~ /^RuleOf5$/i) {
 586     @{$This->{DescriptorNames}} = GetRuleOf5DescriptorNames();
 587   }
 588   elsif ($This->{Mode} =~ /^RuleOf3$/i) {
 589     @{$This->{DescriptorNames}} = GetRuleOf3DescriptorNames();
 590   }
 591   elsif ($This->{Mode} =~ /^Specify$/i) {
 592     if (!@{$This->{DescriptorNames}}) {
 593       croak "Error: ${ClassName}->_CheckAndInitializeDescriptorNames: DescriptorNames must be specified during Specify value for Mode...";
 594     }
 595   }
 596   else {
 597     croak "Error: ${ClassName}->_CheckAndInitializeDescriptorNames: Mode value, $This->{Mode}, is not valid...";
 598   }
 599 }
 600 
 601 # Instantiate descriptor classes corresponding to specified descriptor names...
 602 #
 603 sub _InstantiateDescriptorClasses {
 604   my($This) = @_;
 605   my($DescriptorClassName, $DescriptorName, $DescriptorClassPath);
 606 
 607   $This->{DescriptorClassesInstantiated} = 1;
 608 
 609   @{$This->{DescriptorClassNames}} = ();
 610   %{$This->{DescriptorClassObjects}} = ();
 611 
 612   NAME: for $DescriptorName (@{$This->{DescriptorNames}}) {
 613     $DescriptorClassName = $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName};
 614 
 615     if (exists $This->{DescriptorClassObjects}{$DescriptorClassName}) {
 616       next NAME;
 617     }
 618     push @{$This->{DescriptorClassNames}}, $DescriptorClassName;
 619 
 620     $DescriptorClassPath = $DescriptorsDataMap{ClassNameToClassPath}{$DescriptorClassName};
 621 
 622     if (exists $This->{DescriptorClassParameters}{$DescriptorClassName}) {
 623       $This->{DescriptorClassObjects}{$DescriptorClassName} = $DescriptorClassPath->new(%{$This->{DescriptorClassParameters}{$DescriptorClassName}});
 624     }
 625     else {
 626       $This->{DescriptorClassObjects}{$DescriptorClassName} = $DescriptorClassPath->new();
 627     }
 628   }
 629 
 630   return $This;
 631 }
 632 
 633 # Return a string containg data for MolecularDescriptorsGenerator object...
 634 #
 635 sub StringifyMolecularDescriptorsGenerator {
 636   my($This) = @_;
 637   my($TheString, $NamesAndValuesString, $Name, $Value, @NamesAndValuesInfo, %NamesAndValues);
 638 
 639   # Type of MolecularDescriptors...
 640   $TheString = "MolecularDescriptorsGenerator: Mode - $This->{Mode}; SpecifiedDescriptorNames - < @{$This->{DescriptorNames}} >; AvailableMolecularDescriptorClassNames - < @{$DescriptorsDataMap{ClassNames}} >";
 641 
 642   @NamesAndValuesInfo = ();
 643   %NamesAndValues = $This->GetDescriptorNamesAndValues();
 644 
 645   for $Name (@{$This->{DescriptorNames}}) {
 646     $Value = $NamesAndValues{$Name};
 647     $Value = (TextUtil::IsEmpty($Value) || $Value =~ /^None$/i) ? 'None' : $Value;
 648     push @NamesAndValuesInfo, "$Name - $Value";
 649   }
 650   if (@NamesAndValuesInfo) {
 651     $TheString .= "Names - Values: <" . TextUtil::JoinWords(\@NamesAndValuesInfo, ", ", 0) . ">";
 652   }
 653   else {
 654     $TheString .= "Names - Values: < None>";
 655   }
 656 
 657   return $TheString;
 658 }
 659 
 660 # Is it a MolecularDescriptorsGenerator object?
 661 sub _IsMolecularDescriptorsGenerator {
 662   my($Object) = @_;
 663 
 664   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
 665 }
 666 
 667 # Validate descriptor names for generating descriptor values...
 668 #
 669 sub _ValidateDescriptorNames {
 670   my($This) = @_;
 671   my($DescriptorName);
 672 
 673   for $DescriptorName (@{$This->{DescriptorNames}}) {
 674     if (!exists $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName}) {
 675       croak "Error: ${ClassName}->_SetAndValidateDescriptorNames: Specified descriptor name, $DescriptorName, is not valid...";
 676     }
 677   }
 678 
 679   return $This;
 680 }
 681 
 682 #
 683 # Load available molecular descriptors data...
 684 #
 685 # All available molecular descriptors classes are automatically detected in
 686 # MolecularDescriptors directory under <MayaChemTools>/lib directory and
 687 # information about available descriptor names is retrieved from each descriptor
 688 # class using function GetDescriptorNames. The following %DescriptorsDataMap
 689 # is setup containing all available molecular descriptors data:
 690 #
 691 #   @{$DescriptorsDataMap{ClassNames}}
 692 #   %{$DescriptorsDataMap{ClassNameToPath}}
 693 #   %{$DescriptorsDataMap{ClassToDescriptorNames}}
 694 #   %{$DescriptorsDataMap{DescriptorToClassName}}
 695 #
 696 # GenerateDescriptors method is invoked fo each specified descriptor class
 697 # object to calculate descriptor values for specified descriptors. After successful
 698 # calculation of descriptors, GetDescriptorValues or GetDescriptorValueByName
 699 # methods provided by descriptor objects are used to retrieve calculated
 700 # descriptor values.
 701 #
 702 sub _LoadMolecularDescriptorsData {
 703 
 704   %DescriptorsDataMap = ();
 705 
 706   _RetrieveAndLoadDescriptorClasses();
 707   _SetupDescriptorsDataMap();
 708 }
 709 
 710 #
 711 # Retrieve available molecular descriptors classes from MolecularDescriptors directory under
 712 # <MayaChemTools>/lib directory...
 713 #
 714 sub _RetrieveAndLoadDescriptorClasses {
 715   my($DescriptorsDirName, $MayaChemToolsLibDir, $DescriptorsDirPath, $IncludeDirName, $DescriptorClassName, $DescriptorClassPath, $DescriptorsClassFileName, @FileNames, @DescriptorsClassFileNames);
 716 
 717   @{$DescriptorsDataMap{ClassNames}} = ();
 718   %{$DescriptorsDataMap{ClassNameToPath}} = ();
 719 
 720   $DescriptorsDirName = "MolecularDescriptors";
 721   $MayaChemToolsLibDir = FileUtil::GetMayaChemToolsLibDirName();
 722 
 723   $DescriptorsDirPath = "$MayaChemToolsLibDir/$DescriptorsDirName";
 724 
 725   if (! -d "$DescriptorsDirPath") {
 726     croak "Error: ${ClassName}::_RetrieveAndLoadDescriptorClasses: MayaChemTools package molecular descriptors directory, $DescriptorsDirPath, is missing: Possible installation problems...";
 727   }
 728 
 729   @FileNames = ("$DescriptorsDirPath/*");
 730   $IncludeDirName = 0;
 731   @DescriptorsClassFileNames = FileUtil::ExpandFileNames(\@FileNames, "pm", $IncludeDirName);
 732 
 733   if (!@DescriptorsClassFileNames) {
 734     croak "Error: ${ClassName}::_RetrieveAndLoadDescriptorClasses: MayaChemTools package molecular descriptors directory, $DescriptorsDirPath, doesn't contain any molecular descriptor class: Possible installation problems...";
 735   }
 736 
 737   FILENAME: for $DescriptorsClassFileName (sort @DescriptorsClassFileNames) {
 738     if ($DescriptorsClassFileName !~ /\.pm/) {
 739       croak "Error: ${ClassName}::_RetrieveAndLoadDescriptorClasses: MayaChemTools package molecular descriptors directory, $DescriptorsDirPath, contains invalid class file name $DescriptorsClassFileName: Possible installation problems...";
 740     }
 741 
 742     # Ignore base class and descriptors generator class...
 743     if ($DescriptorsClassFileName =~ /^(MolecularDescriptorsGenerator\.pm|MolecularDescriptors\.pm)$/) {
 744       next FILENAME;
 745     }
 746 
 747     ($DescriptorClassName) = split /\./, $DescriptorsClassFileName;
 748     $DescriptorClassPath = "${DescriptorsDirName}::${DescriptorClassName}";
 749 
 750     # Load descriptors class...
 751     eval "use $DescriptorClassPath";
 752 
 753     if ($@) {
 754       croak "Error: ${ClassName}::_RetrieveAndLoadDescriptorClasses: use $DescriptorClassPath failed: $@ ...";
 755     }
 756 
 757     push @{$DescriptorsDataMap{ClassNames}}, $DescriptorClassName;
 758 
 759     $DescriptorsDataMap{ClassNameToClassPath}{$DescriptorClassName} = $DescriptorClassPath;
 760   }
 761 }
 762 
 763 #
 764 # Setup descriptors data map using loaded descriptor classes...
 765 #
 766 sub _SetupDescriptorsDataMap {
 767   my($DescriptorClassName, $DescriptorName, $DescriptorClassPath, @DescriptorNames);
 768 
 769   # Class to decriptor names map...
 770   %{$DescriptorsDataMap{ClassToDescriptorNames}} = ();
 771 
 772   # Descriptor to class name map...
 773   %{$DescriptorsDataMap{DescriptorToClassName}} = ();
 774 
 775   for $DescriptorClassName (@{$DescriptorsDataMap{ClassNames}}) {
 776     $DescriptorClassPath = $DescriptorsDataMap{ClassNameToClassPath}{$DescriptorClassName};
 777 
 778     @DescriptorNames = $DescriptorClassPath->GetDescriptorNames();
 779 
 780     if (!@DescriptorNames) {
 781       croak "Error: ${ClassName}::_SetupDescriptorsDataMap: Molecular descriptor class $DescriptorClassName doesn't provide any descriptor names...";
 782     }
 783 
 784     if (exists $DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName} ) {
 785       croak "Error: ${ClassName}::_SetupDescriptorsDataMap: Molecular descriptor class $DescriptorClassName has already been processed...";
 786     }
 787 
 788     @{$DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}} = ();
 789     @{$DescriptorsDataMap{ClassToDescriptorNames}{$DescriptorClassName}} = @DescriptorNames;
 790 
 791     for $DescriptorName (@DescriptorNames) {
 792       if (exists $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName}) {
 793         croak "Error: ${ClassName}::_SetupDescriptorsDataMap: Molecular descriptor name, $DescriptorName, in class name, $DescriptorClassName, has already been provided by class name $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName}...";
 794       }
 795 
 796       $DescriptorsDataMap{DescriptorToClassName}{$DescriptorName} = $DescriptorClassName;
 797     }
 798   }
 799 }
 800