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