       1 package MolecularDescriptors::MolecularDescriptorsGenerator;
       2 #
       3 # File: MolecularDescriptorsGenerator.pm
       4 # Author: Manish Sud <msud@san.rr.com>
       5 #
       6 # Copyright (C) 2018 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