MayaChemTools

   1 #!/usr/bin/perl -w
   2 #
   3 # $RCSfile: CalculatePhysicochemicalProperties.pl,v $
   4 # $Date: 2017/01/12 18:56:31 $
   5 # $Revision: 1.22 $
   6 #
   7 # Author: Manish Sud <msud@san.rr.com>
   8 #
   9 # Copyright (C) 2017 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 FindBin; use lib "$FindBin::Bin/../lib";
  31 use Getopt::Long;
  32 use File::Basename;
  33 use Text::ParseWords;
  34 use Benchmark;
  35 use FileUtil;
  36 use TextUtil;
  37 use SDFileUtil;
  38 use MoleculeFileIO;
  39 use Molecule;
  40 use AtomTypes::AtomicInvariantsAtomTypes;
  41 use AtomTypes::FunctionalClassAtomTypes;
  42 use MolecularDescriptors::MolecularDescriptorsGenerator;
  43 
  44 my($ScriptName, %Options, $StartTime, $EndTime, $TotalTime);
  45 
  46 # Autoflush STDOUT
  47 $| = 1;
  48 
  49 # Starting message...
  50 $ScriptName = basename($0);
  51 print "\n$ScriptName: Starting...\n\n";
  52 $StartTime = new Benchmark;
  53 
  54 # Get the options and setup script...
  55 SetupScriptUsage();
  56 if ($Options{help} || @ARGV < 1) {
  57   die GetUsageFromPod("$FindBin::Bin/$ScriptName");
  58 }
  59 
  60 my(@SDFilesList);
  61 @SDFilesList = ExpandFileNames(\@ARGV, "sdf sd");
  62 
  63 # Process options...
  64 print "Processing options...\n";
  65 my(%OptionsInfo);
  66 ProcessOptions();
  67 
  68 # Setup information about input files...
  69 print "Checking input SD file(s)...\n";
  70 my(%SDFilesInfo);
  71 RetrieveSDFilesInfo();
  72 
  73 # Process input files..
  74 my($FileIndex);
  75 if (@SDFilesList > 1) {
  76   print "\nProcessing SD files...\n";
  77 }
  78 for $FileIndex (0 .. $#SDFilesList) {
  79   if ($SDFilesInfo{FileOkay}[$FileIndex]) {
  80     print "\nProcessing file $SDFilesList[$FileIndex]...\n";
  81     CalculatePhysicochemicalProperties($FileIndex);
  82   }
  83 }
  84 print "\n$ScriptName:Done...\n\n";
  85 
  86 $EndTime = new Benchmark;
  87 $TotalTime = timediff ($EndTime, $StartTime);
  88 print "Total time: ", timestr($TotalTime), "\n";
  89 
  90 ###############################################################################
  91 
  92 # Calculate physicochemical properties for a SD file...
  93 #
  94 sub CalculatePhysicochemicalProperties {
  95   my($FileIndex) = @_;
  96   my($CmpdCount, $IgnoredCmpdCount, $RuleOf5ViolationsCount, $RuleOf3ViolationsCount, $SDFile, $MoleculeFileIO, $Molecule, $MolecularDescriptorsGenerator, $PhysicochemicalPropertiesDataRef, $NewSDFileRef, $NewTextFileRef);
  97 
  98   $SDFile = $SDFilesList[$FileIndex];
  99 
 100   # Setup output files...
 101   $NewSDFileRef = ''; $NewTextFileRef = '';
 102   ($NewSDFileRef, $NewTextFileRef) = SetupAndOpenOutputFiles($FileIndex);
 103 
 104   # Setup molecular descriptor generator to calculate property values for specifed
 105   # property names...
 106   $MolecularDescriptorsGenerator = SetupMolecularDescriptorsGenerator();
 107 
 108   ($CmpdCount, $IgnoredCmpdCount, $RuleOf5ViolationsCount, $RuleOf3ViolationsCount) = ('0') x 4;
 109 
 110   $MoleculeFileIO = new MoleculeFileIO('Name' => $SDFile);
 111   $MoleculeFileIO->Open();
 112 
 113   COMPOUND: while ($Molecule = $MoleculeFileIO->ReadMolecule()) {
 114     $CmpdCount++;
 115 
 116     # Filter compound data before calculating physiochemical properties...
 117     if ($OptionsInfo{Filter}) {
 118       if (CheckAndFilterCompound($CmpdCount, $Molecule)) {
 119         $IgnoredCmpdCount++;
 120         next COMPOUND;
 121       }
 122     }
 123 
 124     # Calculate properties...
 125     $PhysicochemicalPropertiesDataRef = CalculateMoleculeProperties($MolecularDescriptorsGenerator, $Molecule);
 126 
 127     if (!defined($PhysicochemicalPropertiesDataRef)) {
 128       $IgnoredCmpdCount++;
 129       ProcessIgnoredCompound('PropertiesCalculationFailed', $CmpdCount, $Molecule);
 130       next COMPOUND;
 131     }
 132 
 133     # Calculate any rule violations...
 134     if ($OptionsInfo{RuleOf5Violations} && $PhysicochemicalPropertiesDataRef->{RuleOf5Violations}) {
 135       $RuleOf5ViolationsCount++;
 136     }
 137 
 138     if ($OptionsInfo{RuleOf3Violations} && $PhysicochemicalPropertiesDataRef->{RuleOf3Violations}) {
 139       $RuleOf3ViolationsCount++;
 140     }
 141 
 142     # Write out calculate properties...
 143     WriteDataToOutputFiles($FileIndex, $CmpdCount, $Molecule, $PhysicochemicalPropertiesDataRef, $NewSDFileRef, $NewTextFileRef);
 144   }
 145   $MoleculeFileIO->Close();
 146 
 147   if ($OptionsInfo{SDOutput} && $NewSDFileRef) {
 148     close $NewSDFileRef;
 149   }
 150   if ($OptionsInfo{TextOutput} && $NewTextFileRef) {
 151     close $NewTextFileRef;
 152   }
 153 
 154   WriteCalculationSummaryStatistics($CmpdCount, $IgnoredCmpdCount, $RuleOf5ViolationsCount, $RuleOf3ViolationsCount);
 155 }
 156 
 157 # Process compound being ignored due to problems in physicochemical properties calculation...
 158 #
 159 sub ProcessIgnoredCompound {
 160   my($Mode, $CmpdCount, $Molecule) = @_;
 161   my($CmpdID, $DataFieldLabelAndValuesRef);
 162 
 163   $DataFieldLabelAndValuesRef = $Molecule->GetDataFieldLabelAndValues();
 164   $CmpdID = SetupCmpdIDForTextFiles($CmpdCount, $Molecule, $DataFieldLabelAndValuesRef);
 165 
 166   MODE: {
 167     if ($Mode =~ /^ContainsNonElementalData$/i) {
 168       warn "\nWarning: Ignoring compound record number $CmpdCount with ID $CmpdID: Compound contains atom data corresponding to non-elemental atom symbol(s)...\n\n";
 169       next MODE;
 170     }
 171 
 172     if ($Mode =~ /^ContainsNoElementalData$/i) {
 173       warn "\nWarning: Ignoring compound record number $CmpdCount with ID $CmpdID: Compound contains no atom data...\n\n";
 174       next MODE;
 175     }
 176 
 177     if ($Mode =~ /^PropertiesCalculationFailed$/i) {
 178       warn "\nWarning: Ignoring compound record number $CmpdCount with ID $CmpdID: Physicochemical properties calculation didn't succeed...\n\n";
 179       next MODE;
 180     }
 181     warn "\nWarning: Ignoring compound record number $CmpdCount with ID $CmpdID: Physicochemical properties calculation didn't succeed...\n\n";
 182   }
 183 }
 184 
 185 # Check and filter compounds....
 186 #
 187 sub CheckAndFilterCompound {
 188   my($CmpdCount, $Molecule) = @_;
 189   my($ElementCount, $NonElementCount);
 190 
 191   ($ElementCount, $NonElementCount) = $Molecule->GetNumOfElementsAndNonElements();
 192 
 193   if ($NonElementCount) {
 194     ProcessIgnoredCompound('ContainsNonElementalData', $CmpdCount, $Molecule);
 195     return 1;
 196   }
 197 
 198   if (!$ElementCount) {
 199     ProcessIgnoredCompound('ContainsNoElementalData', $CmpdCount, $Molecule);
 200     return 1;
 201   }
 202 
 203   return 0;
 204 }
 205 
 206 # Write out compounds physicochemical properties calculation summary statistics...
 207 #
 208 sub WriteCalculationSummaryStatistics {
 209   my($CmpdCount, $IgnoredCmpdCount, $RuleOf5ViolationsCount, $RuleOf3ViolationsCount) = @_;
 210   my($ProcessedCmpdCount);
 211 
 212   $ProcessedCmpdCount = $CmpdCount - $IgnoredCmpdCount;
 213 
 214   print "\nNumber of compounds: $CmpdCount\n";
 215   print "Number of compounds processed successfully during physicochemical properties calculation: $ProcessedCmpdCount\n";
 216   print "Number of compounds ignored during physicochemical properties calculation: $IgnoredCmpdCount\n";
 217 
 218   if ($OptionsInfo{RuleOf5Violations}) {
 219     print "Number of compounds with one or more RuleOf5 violations: $RuleOf5ViolationsCount\n";
 220   }
 221 
 222   if ($OptionsInfo{RuleOf3Violations}) {
 223     print "Number of compounds with one or more RuleOf3 violations: $RuleOf3ViolationsCount\n";
 224   }
 225 
 226 }
 227 
 228 # Open output files...
 229 #
 230 sub SetupAndOpenOutputFiles {
 231   my($FileIndex) = @_;
 232   my($NewSDFile, $NewTextFile, $NewSDFileRef, $NewTextFileRef);
 233 
 234   $NewSDFileRef = '';
 235   $NewTextFileRef = '';
 236 
 237   if ($OptionsInfo{SDOutput}) {
 238     $NewSDFile = $SDFilesInfo{SDOutFileNames}[$FileIndex];
 239     print "Generating SD file $NewSDFile...\n";
 240     open NEWSDFILE, ">$NewSDFile" or die "Error: Couldn't open $NewSDFile: $! \n";
 241     $NewSDFileRef = \*NEWSDFILE;
 242   }
 243   if ($OptionsInfo{TextOutput}) {
 244     $NewTextFile = $SDFilesInfo{TextOutFileNames}[$FileIndex];
 245     print "Generating text file $NewTextFile...\n";
 246     open NEWTEXTFILE, ">$NewTextFile" or die "Error: Couldn't open $NewTextFile: $! \n";
 247     WriteTextFileCoulmnLabels($FileIndex, \*NEWTEXTFILE);
 248     $NewTextFileRef = \*NEWTEXTFILE;
 249   }
 250   return ($NewSDFileRef, $NewTextFileRef);
 251 }
 252 
 253 # Write calculated physicochemical properties and other data to appropriate output files...
 254 #
 255 sub WriteDataToOutputFiles {
 256   my($FileIndex, $CmpdCount, $Molecule, $PhysicochemicalPropertiesDataRef, $NewSDFileRef, $NewTextFileRef) = @_;
 257   my($PropertyName, $PropertyValue);
 258 
 259   if ($OptionsInfo{SDOutput}) {
 260     # Retrieve input compound string used to create molecule and write it out
 261     # without last line containing a delimiter...
 262     my($CmpdString);
 263     $CmpdString = $Molecule->GetInputMoleculeString();
 264     $CmpdString =~ s/\$\$\$\$$//;
 265     print $NewSDFileRef "$CmpdString";
 266 
 267     # Write out calculated physicochemical properties data...
 268     for $PropertyName (@{$OptionsInfo{SpecifiedPropertyNames}}) {
 269       $PropertyValue = $PhysicochemicalPropertiesDataRef->{$PropertyName};
 270       print $NewSDFileRef  ">  <$PropertyName>\n$PropertyValue\n\n";
 271     }
 272 
 273     # Write out RuleOf5 violations for molecule....
 274     if ($OptionsInfo{RuleOf5Violations}) {
 275       $PropertyValue = $PhysicochemicalPropertiesDataRef->{RuleOf5Violations};
 276       print $NewSDFileRef  ">  <RuleOf5Violations>\n$PropertyValue\n\n";
 277     }
 278 
 279     # Write out RuleOf3 violations for molecule....
 280     if ($OptionsInfo{RuleOf3Violations}) {
 281       $PropertyValue = $PhysicochemicalPropertiesDataRef->{RuleOf3Violations};
 282       print $NewSDFileRef  ">  <RuleOf3Violations>\n$PropertyValue\n\n";
 283     }
 284 
 285     # Write out delimiter...
 286     print $NewSDFileRef "\$\$\$\$\n";
 287   }
 288 
 289   if ($OptionsInfo{TextOutput}) {
 290     my($Line, $DataFieldLabelAndValuesRef, $DataFieldLabel, $DataFieldValue, @LineWords,);
 291 
 292     $DataFieldLabelAndValuesRef = $Molecule->GetDataFieldLabelAndValues();
 293     @LineWords = ();
 294     if ($OptionsInfo{DataFieldsMode} =~ /^CompoundID$/i) {
 295       push @LineWords, SetupCmpdIDForTextFiles($CmpdCount, $Molecule, $DataFieldLabelAndValuesRef);
 296     }
 297     elsif ($OptionsInfo{DataFieldsMode} =~ /^All$/i) {
 298       @LineWords = map { exists $DataFieldLabelAndValuesRef->{$_} ? $DataFieldLabelAndValuesRef->{$_} : ''} @{$SDFilesInfo{AllDataFieldsRef}[$FileIndex]};
 299     }
 300     elsif ($OptionsInfo{DataFieldsMode} =~ /^Common$/i) {
 301       @LineWords = map { exists $DataFieldLabelAndValuesRef->{$_} ? $DataFieldLabelAndValuesRef->{$_} : ''} @{$SDFilesInfo{CommonDataFieldsRef}[$FileIndex]};
 302     }
 303     elsif ($OptionsInfo{DataFieldsMode} =~ /^Specify$/i) {
 304       @LineWords = map { exists $DataFieldLabelAndValuesRef->{$_} ? $DataFieldLabelAndValuesRef->{$_} : ''} @{$OptionsInfo{SpecifiedDataFields}};
 305     }
 306 
 307     # Append calculated physicochemical properties data...
 308     for $PropertyName (@{$OptionsInfo{SpecifiedPropertyNames}}) {
 309       $PropertyValue = $PhysicochemicalPropertiesDataRef->{$PropertyName};
 310       push @LineWords, $PropertyValue;
 311     }
 312 
 313     # Write out RuleOf5 violations for molecule....
 314     if ($OptionsInfo{RuleOf5Violations}) {
 315       $PropertyValue = $PhysicochemicalPropertiesDataRef->{RuleOf5Violations};
 316       push @LineWords, $PropertyValue;
 317     }
 318 
 319     # Write out RuleOf3 violations for molecule....
 320     if ($OptionsInfo{RuleOf3Violations}) {
 321       $PropertyValue = $PhysicochemicalPropertiesDataRef->{RuleOf3Violations};
 322       push @LineWords, $PropertyValue;
 323     }
 324 
 325     $Line = JoinWords(\@LineWords, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
 326     print $NewTextFileRef "$Line\n";
 327   }
 328 }
 329 
 330 # Write out approriate column labels to text file...
 331 sub WriteTextFileCoulmnLabels {
 332   my($FileIndex, $NewTextFileRef) = @_;
 333   my($Line, @LineWords);
 334 
 335   @LineWords = ();
 336   if ($OptionsInfo{DataFieldsMode} =~ /^All$/i) {
 337     push @LineWords, @{$SDFilesInfo{AllDataFieldsRef}[$FileIndex]};
 338   }
 339   elsif ($OptionsInfo{DataFieldsMode} =~ /^Common$/i) {
 340     push @LineWords, @{$SDFilesInfo{CommonDataFieldsRef}[$FileIndex]};
 341   }
 342   elsif ($OptionsInfo{DataFieldsMode} =~ /^Specify$/i) {
 343     push @LineWords, @{$OptionsInfo{SpecifiedDataFields}};
 344   }
 345   elsif ($OptionsInfo{DataFieldsMode} =~ /^CompoundID$/i) {
 346     push @LineWords, $OptionsInfo{CompoundIDLabel};
 347   }
 348   my($SpecifiedPropertyName);
 349 
 350   # Append physicochemical properties column labels...
 351   push @LineWords,  @{$OptionsInfo{SpecifiedPropertyNames}};
 352 
 353   # Write out RuleOf5 violations label...
 354   if ($OptionsInfo{RuleOf5Violations}) {
 355     push @LineWords, 'RuleOf5Violations';
 356   }
 357 
 358   # Write out RuleOf3 violations label...
 359   if ($OptionsInfo{RuleOf3Violations}) {
 360     push @LineWords, 'RuleOf3Violations';
 361   }
 362 
 363   $Line = JoinWords(\@LineWords, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
 364   print $NewTextFileRef "$Line\n";
 365 }
 366 
 367 # Generate compound ID for text files..
 368 #
 369 sub SetupCmpdIDForTextFiles {
 370   my($CmpdCount, $Molecule, $DataFieldLabelAndValuesRef) = @_;
 371   my($CmpdID);
 372 
 373   $CmpdID = '';
 374   if ($OptionsInfo{CompoundIDMode} =~ /^MolNameOrLabelPrefix$/i) {
 375     my($MolName);
 376     $MolName = $Molecule->GetName();
 377     $CmpdID = $MolName ? $MolName : "$OptionsInfo{CompoundID}${CmpdCount}";
 378   }
 379   elsif ($OptionsInfo{CompoundIDMode} =~ /^LabelPrefix$/i) {
 380     $CmpdID = "$OptionsInfo{CompoundID}${CmpdCount}";
 381   }
 382   elsif ($OptionsInfo{CompoundIDMode} =~ /^DataField$/i) {
 383     my($SpecifiedDataField);
 384     $SpecifiedDataField = $OptionsInfo{CompoundID};
 385     $CmpdID = exists $DataFieldLabelAndValuesRef->{$SpecifiedDataField} ? $DataFieldLabelAndValuesRef->{$SpecifiedDataField} : '';
 386   }
 387   elsif ($OptionsInfo{CompoundIDMode} =~ /^MolName$/i) {
 388     $CmpdID = $Molecule->GetName();
 389   }
 390   return $CmpdID;
 391 }
 392 
 393 # Calculate physicochemical properties for molecule...
 394 #
 395 sub CalculateMoleculeProperties {
 396   my($MolecularDescriptorsGenerator, $Molecule) = @_;
 397   my($PropertyName, $PropertyValue, $MolecularDescriptorsObject, %CalculatedPhysicochemicalProperties);
 398 
 399   %CalculatedPhysicochemicalProperties = ();
 400 
 401   if ($OptionsInfo{KeepLargestComponent}) {
 402     $Molecule->KeepLargestComponent();
 403   }
 404 
 405   if (!$Molecule->DetectRings()) {
 406     return undef;
 407   }
 408   $Molecule->SetAromaticityModel($OptionsInfo{AromaticityModel});
 409   $Molecule->DetectAromaticity();
 410 
 411   if ($OptionsInfo{AddHydrogens}) {
 412     $Molecule->AddHydrogens();
 413   }
 414 
 415   # Calculate physicochemical properties...
 416   $MolecularDescriptorsGenerator->SetMolecule($Molecule);
 417   $MolecularDescriptorsGenerator->GenerateDescriptors();
 418 
 419   if (!$MolecularDescriptorsGenerator->IsDescriptorsGenerationSuccessful()) {
 420     return undef;
 421   }
 422 
 423   %CalculatedPhysicochemicalProperties = $MolecularDescriptorsGenerator->GetDescriptorNamesAndValues();
 424 
 425   # Count RuleOf3 violations...
 426   if ($OptionsInfo{RuleOf3Violations}) {
 427     CalculateRuleViolationsCount('RuleOf3Violations', \%CalculatedPhysicochemicalProperties);
 428   }
 429 
 430   # Count RuleOf5 violations...
 431   if ($OptionsInfo{RuleOf5Violations}) {
 432     CalculateRuleViolationsCount('RuleOf5Violations', \%CalculatedPhysicochemicalProperties);
 433   }
 434 
 435   return \%CalculatedPhysicochemicalProperties;
 436 }
 437 
 438 # Setup molecular descriptor generator to calculate property values for specifed
 439 # property names...
 440 #
 441 sub SetupMolecularDescriptorsGenerator {
 442   my($PropertyName, $MolecularDescriptorsGenerator);
 443 
 444   $MolecularDescriptorsGenerator = new MolecularDescriptors::MolecularDescriptorsGenerator('Mode' => 'Specify', 'DescriptorNames' => \@{$OptionsInfo{SpecifiedPropertyNames}});
 445 
 446   # Setup molecular desciptor calculation parameters...
 447   if (exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('MolecularWeight')}) || exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('ExactMass')}) ) {
 448     $MolecularDescriptorsGenerator->SetDescriptorClassParameters('DescriptorClassName' => 'WeightAndMassDescriptors', %{$OptionsInfo{PrecisionParametersMap}});
 449   }
 450 
 451   if (exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('RotatableBonds')})) {
 452     $MolecularDescriptorsGenerator->SetDescriptorClassParameters('DescriptorClassName' => 'RotatableBondsDescriptors', %{$OptionsInfo{RotatableBondsParametersMap}});
 453   }
 454 
 455   if (exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('HydrogenBondDonors')}) || exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('HydrogenBondAcceptors')}) ) {
 456     $MolecularDescriptorsGenerator->SetDescriptorClassParameters('DescriptorClassName' => 'HydrogenBondsDescriptors', 'HydrogenBondsType' => $OptionsInfo{HydrogenBonds});
 457   }
 458 
 459   if (exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('TPSA')})) {
 460     $MolecularDescriptorsGenerator->SetDescriptorClassParameters('DescriptorClassName' => 'TPSADescriptors', %{$OptionsInfo{TPSAParametersMap}});
 461   }
 462 
 463   if (exists($OptionsInfo{SpecifiedPropertyNamesMap}{lc('MolecularComplexity')})) {
 464     $MolecularDescriptorsGenerator->SetDescriptorClassParameters('DescriptorClassName' => 'MolecularComplexityDescriptors', %{$OptionsInfo{MolecularComplexityParametersMap}});
 465   }
 466 
 467   return $MolecularDescriptorsGenerator;
 468 }
 469 
 470 # Calculate RuleOf3 or RuleOf5 violations count...
 471 #
 472 sub CalculateRuleViolationsCount {
 473   my($RuleViolationsType, $CalculatedPropertiesMapRef) = @_;
 474   my($RuleViolationsCount, $PropertyName);
 475 
 476   $RuleViolationsCount = 0;
 477 
 478   RULEVIOLATIONSTYPE: {
 479     if ($RuleViolationsType =~ /^RuleOf3Violations$/i) {
 480       for $PropertyName (@{$OptionsInfo{RuleOf3PropertyNames}}) {
 481         if ($CalculatedPropertiesMapRef->{$PropertyName} > $OptionsInfo{RuleOf3MaxPropertyValuesMap}{$PropertyName}) {
 482           $RuleViolationsCount++;
 483         }
 484       }
 485       last RULEVIOLATIONSTYPE;
 486     }
 487 
 488     if ($RuleViolationsType =~ /^RuleOf5Violations$/i) {
 489       for $PropertyName (@{$OptionsInfo{RuleOf5PropertyNames}}) {
 490         if ($CalculatedPropertiesMapRef->{$PropertyName} > $OptionsInfo{RuleOf5MaxPropertyValuesMap}{$PropertyName}) {
 491           $RuleViolationsCount++;
 492         }
 493       }
 494       last RULEVIOLATIONSTYPE;
 495     }
 496 
 497     die "Warning: Unknown rule violation type: $RuleViolationsType...";
 498   }
 499 
 500   # Set rule violation count...
 501   $CalculatedPropertiesMapRef->{$RuleViolationsType} = $RuleViolationsCount;
 502 
 503 }
 504 
 505 # Retrieve information about SD files...
 506 #
 507 sub RetrieveSDFilesInfo {
 508   my($SDFile, $Index, $FileDir, $FileExt, $FileName, $OutFileRoot, $TextOutFileExt, $SDOutFileExt, $NewSDFileName, $NewTextFileName, $CheckDataField, $CollectDataFields, $AllDataFieldsRef, $CommonDataFieldsRef);
 509 
 510   %SDFilesInfo = ();
 511   @{$SDFilesInfo{FileOkay}} = ();
 512   @{$SDFilesInfo{OutFileRoot}} = ();
 513   @{$SDFilesInfo{SDOutFileNames}} = ();
 514   @{$SDFilesInfo{TextOutFileNames}} = ();
 515   @{$SDFilesInfo{AllDataFieldsRef}} = ();
 516   @{$SDFilesInfo{CommonDataFieldsRef}} = ();
 517 
 518   $CheckDataField = ($OptionsInfo{TextOutput} && ($OptionsInfo{DataFieldsMode} =~ /^CompoundID$/i) && ($OptionsInfo{CompoundIDMode} =~ /^DataField$/i)) ? 1 : 0;
 519   $CollectDataFields = ($OptionsInfo{TextOutput} && ($OptionsInfo{DataFieldsMode} =~ /^(All|Common)$/i)) ? 1 : 0;
 520 
 521   FILELIST: for $Index (0 .. $#SDFilesList) {
 522     $SDFile = $SDFilesList[$Index];
 523 
 524     $SDFilesInfo{FileOkay}[$Index] = 0;
 525     $SDFilesInfo{OutFileRoot}[$Index] = '';
 526     $SDFilesInfo{SDOutFileNames}[$Index] = '';
 527     $SDFilesInfo{TextOutFileNames}[$Index] = '';
 528 
 529     $SDFile = $SDFilesList[$Index];
 530     if (!(-e $SDFile)) {
 531       warn "Warning: Ignoring file $SDFile: It doesn't exist\n";
 532       next FILELIST;
 533     }
 534     if (!CheckFileType($SDFile, "sd sdf")) {
 535       warn "Warning: Ignoring file $SDFile: It's not a SD file\n";
 536       next FILELIST;
 537     }
 538 
 539     if ($CheckDataField) {
 540       # Make sure data field exists in SD file..
 541       my($CmpdString, $SpecifiedDataField, @CmpdLines, %DataFieldValues);
 542 
 543       @CmpdLines = ();
 544       open SDFILE, "$SDFile" or die "Error: Couldn't open $SDFile: $! \n";
 545       $CmpdString = ReadCmpdString(\*SDFILE);
 546       close SDFILE;
 547       @CmpdLines = split "\n", $CmpdString;
 548       %DataFieldValues = GetCmpdDataHeaderLabelsAndValues(\@CmpdLines);
 549       $SpecifiedDataField = $OptionsInfo{CompoundID};
 550       if (!exists $DataFieldValues{$SpecifiedDataField}) {
 551         warn "Warning: Ignoring file $SDFile: Data field value, $SpecifiedDataField, using  \"--CompoundID\" option in \"DataField\" \"--CompoundIDMode\" doesn't exist\n";
 552         next FILELIST;
 553       }
 554     }
 555 
 556     $AllDataFieldsRef = '';
 557     $CommonDataFieldsRef = '';
 558     if ($CollectDataFields) {
 559       my($CmpdCount);
 560       open SDFILE, "$SDFile" or die "Error: Couldn't open $SDFile: $! \n";
 561       ($CmpdCount, $AllDataFieldsRef, $CommonDataFieldsRef) = GetAllAndCommonCmpdDataHeaderLabels(\*SDFILE);
 562       close SDFILE;
 563     }
 564 
 565     # Setup output file names...
 566     $FileDir = ""; $FileName = ""; $FileExt = "";
 567     ($FileDir, $FileName, $FileExt) = ParseFileName($SDFile);
 568 
 569     $TextOutFileExt = "csv";
 570     if ($Options{outdelim} =~ /^tab$/i) {
 571       $TextOutFileExt = "tsv";
 572     }
 573     $SDOutFileExt = $FileExt;
 574 
 575     if ($OptionsInfo{OutFileRoot} && (@SDFilesList == 1)) {
 576       my ($RootFileDir, $RootFileName, $RootFileExt) = ParseFileName($OptionsInfo{OutFileRoot});
 577       if ($RootFileName && $RootFileExt) {
 578         $FileName = $RootFileName;
 579       }
 580       else {
 581         $FileName = $OptionsInfo{OutFileRoot};
 582       }
 583       $OutFileRoot = $FileName;
 584     }
 585     else {
 586       $OutFileRoot = "${FileName}PhysicochemicalProperties";
 587     }
 588 
 589     $NewSDFileName = "${OutFileRoot}.${SDOutFileExt}";
 590     $NewTextFileName = "${OutFileRoot}.${TextOutFileExt}";
 591 
 592     if ($OptionsInfo{SDOutput}) {
 593       if ($SDFile =~ /$NewSDFileName/i) {
 594         warn "Warning: Ignoring input file $SDFile: Same output, $NewSDFileName, and input file names.\n";
 595         print "Specify a different name using \"-r --root\" option or use default name.\n";
 596         next FILELIST;
 597       }
 598     }
 599 
 600     if (!$OptionsInfo{OverwriteFiles}) {
 601       # Check SD and text outout files...
 602       if ($OptionsInfo{SDOutput}) {
 603         if (-e $NewSDFileName) {
 604           warn "Warning: Ignoring file $SDFile: The file $NewSDFileName already exists\n";
 605           next FILELIST;
 606         }
 607       }
 608       if ($OptionsInfo{TextOutput}) {
 609         if (-e $NewTextFileName) {
 610           warn "Warning: Ignoring file $SDFile: The file $NewTextFileName already exists\n";
 611           next FILELIST;
 612         }
 613       }
 614     }
 615 
 616     $SDFilesInfo{FileOkay}[$Index] = 1;
 617 
 618     $SDFilesInfo{OutFileRoot}[$Index] = $OutFileRoot;
 619     $SDFilesInfo{SDOutFileNames}[$Index] = $NewSDFileName;
 620     $SDFilesInfo{TextOutFileNames}[$Index] = $NewTextFileName;
 621 
 622     $SDFilesInfo{AllDataFieldsRef}[$Index] = $AllDataFieldsRef;
 623     $SDFilesInfo{CommonDataFieldsRef}[$Index] = $CommonDataFieldsRef;
 624   }
 625 }
 626 
 627 # Process option values...
 628 sub ProcessOptions {
 629   %OptionsInfo = ();
 630 
 631   $OptionsInfo{AromaticityModel} = $Options{aromaticitymodel};
 632 
 633   # Process property name related options...
 634   ProcessPropertyNamesOption();
 635 
 636   # Setup RuleOf3 and RuleOf5 violation calculations...
 637   $OptionsInfo{RuleOf3Violations} = ($Options{ruleof3violations} =~ /^Yes$/i) ? 1 : 0;
 638   $OptionsInfo{RuleOf5Violations} = ($Options{ruleof5violations} =~ /^Yes$/i) ? 1 : 0;
 639 
 640   $OptionsInfo{CompoundIDMode} = $Options{compoundidmode};
 641   $OptionsInfo{CompoundIDLabel} = $Options{compoundidlabel};
 642   $OptionsInfo{DataFieldsMode} = $Options{datafieldsmode};
 643 
 644   my(@SpecifiedDataFields);
 645   @SpecifiedDataFields = ();
 646 
 647   @{$OptionsInfo{SpecifiedDataFields}} = ();
 648   $OptionsInfo{CompoundID} = '';
 649 
 650   if ($Options{datafieldsmode} =~ /^CompoundID$/i) {
 651     if ($Options{compoundidmode} =~ /^DataField$/i) {
 652       if (!$Options{compoundid}) {
 653         die "Error: You must specify a value for \"--CompoundID\" option in \"DataField\" \"--CompoundIDMode\". \n";
 654       }
 655       $OptionsInfo{CompoundID} = $Options{compoundid};
 656     }
 657     elsif ($Options{compoundidmode} =~ /^(LabelPrefix|MolNameOrLabelPrefix)$/i) {
 658       $OptionsInfo{CompoundID} = $Options{compoundid} ? $Options{compoundid} : 'Cmpd';
 659     }
 660   }
 661   elsif ($Options{datafieldsmode} =~ /^Specify$/i) {
 662     if (!$Options{datafields}) {
 663       die "Error: You must specify a value for \"--DataFields\" option in \"Specify\" \"-d, --DataFieldsMode\". \n";
 664     }
 665     @SpecifiedDataFields = split /\,/, $Options{datafields};
 666     push @{$OptionsInfo{SpecifiedDataFields}}, @SpecifiedDataFields;
 667   }
 668 
 669   # Types of hydrogen bonds...
 670   $OptionsInfo{HydrogenBonds} = $Options{hydrogenbonds};
 671 
 672   # Process precision value parameters...
 673   ProcessPrecisionOption();
 674 
 675   # Process rotatable bonds parameters...
 676   ProcessRotatableBondsOption();
 677 
 678   # Process TPSA parameters...
 679   ProcessTPSAOption();
 680 
 681   # Process molecular complexity parameters...
 682   ProcessMolecularComplexityOption();
 683 
 684   $OptionsInfo{Filter} = ($Options{filter} =~ /^Yes$/i) ? 1 : 0;
 685 
 686   $OptionsInfo{KeepLargestComponent} = ($Options{keeplargestcomponent} =~ /^Yes$/i) ? 1 : 0;
 687 
 688   $OptionsInfo{Output} = $Options{output};
 689   $OptionsInfo{SDOutput} = ($Options{output} =~ /^(SD|Both)$/i) ? 1 : 0;
 690   $OptionsInfo{TextOutput} = ($Options{output} =~ /^(Text|Both)$/i) ? 1 : 0;
 691 
 692   $OptionsInfo{OutDelim} = ($Options{outdelim} =~ /tab/i ) ? "\t" : (($Options{outdelim} =~ /semicolon/i) ? "\;" : "\,");
 693   $OptionsInfo{OutQuote} = ($Options{quote} =~ /^Yes$/i) ? 1 : 0;
 694 
 695   $OptionsInfo{OverwriteFiles} = $Options{overwrite} ? 1 : 0;
 696   $OptionsInfo{OutFileRoot} = $Options{root} ? $Options{root} : 0;
 697 }
 698 
 699 # Process property name related options...
 700 #
 701 sub ProcessPropertyNamesOption {
 702 
 703   # Setup supported physicochemical properties...
 704   my($SupportedProperty);
 705 
 706   @{$OptionsInfo{SupportedPropertyNames}} = ();
 707   %{$OptionsInfo{SupportedPropertyNamesMap}} = ();
 708 
 709   @{$OptionsInfo{RuleOf5PropertyNames}} = ();
 710   %{$OptionsInfo{RuleOf5MaxPropertyValuesMap}} = ();
 711 
 712   @{$OptionsInfo{RuleOf3PropertyNames}} = ();
 713   %{$OptionsInfo{RuleOf3MaxPropertyValuesMap}} = ();
 714 
 715   @{$OptionsInfo{DefaultPropertyNames}} = ();
 716 
 717   @{$OptionsInfo{SupportedPropertyNames}} = qw(MolecularWeight ExactMass HeavyAtoms Rings AromaticRings MolecularVolume RotatableBonds HydrogenBondDonors HydrogenBondAcceptors SLogP SMR TPSA Fsp3Carbons Sp3Carbons MolecularComplexity);
 718 
 719   @{$OptionsInfo{RuleOf5PropertyNames}} = qw(MolecularWeight HydrogenBondDonors HydrogenBondAcceptors SLogP);
 720   %{$OptionsInfo{RuleOf5MaxPropertyValuesMap}} = ('MolecularWeight' => 500, 'HydrogenBondDonors' => 5, 'HydrogenBondAcceptors' => 10,  'SLogP' => 5);
 721 
 722   @{$OptionsInfo{RuleOf3PropertyNames}} = qw(MolecularWeight RotatableBonds HydrogenBondDonors HydrogenBondAcceptors SLogP TPSA);
 723   %{$OptionsInfo{RuleOf3MaxPropertyValuesMap}} = ('MolecularWeight' => 300, 'RotatableBonds' => 3, 'HydrogenBondDonors' => 3, 'HydrogenBondAcceptors' => 3, 'SLogP' => 3, 'TPSA' => 60);
 724 
 725   @{$OptionsInfo{DefaultPropertyNames}} = qw(MolecularWeight HeavyAtoms MolecularVolume RotatableBonds HydrogenBondDonors HydrogenBondAcceptors SLogP TPSA);
 726 
 727   for $SupportedProperty (@{$OptionsInfo{SupportedPropertyNames}}) {
 728     $OptionsInfo{SupportedPropertyNamesMap}{lc($SupportedProperty)} = $SupportedProperty;
 729   }
 730 
 731   # Process specified properties....
 732   my($SpecifiedPropertyName, @SpecifiedPropertyNames, %SpecifiedPropertyNamesMap);
 733 
 734   @SpecifiedPropertyNames = ();
 735   %SpecifiedPropertyNamesMap = ();
 736 
 737   @{$OptionsInfo{SpecifiedPropertyNames}} = ();
 738   %{$OptionsInfo{SpecifiedPropertyNamesMap}} = ();
 739 
 740   if ($Options{mode} =~ /^All$/i) {
 741     @SpecifiedPropertyNames = @{$OptionsInfo{SupportedPropertyNames}};
 742   }
 743   elsif ($Options{mode} =~ /^RuleOf5$/i) {
 744     @SpecifiedPropertyNames = @{$OptionsInfo{RuleOf5PropertyNames}};
 745   }
 746   elsif ($Options{mode} =~ /^RuleOf3$/i) {
 747     @SpecifiedPropertyNames = @{$OptionsInfo{RuleOf3PropertyNames}};
 748   }
 749   elsif (IsEmpty($Options{mode})) {
 750     @SpecifiedPropertyNames = @{$OptionsInfo{DefaultPropertyNames}};
 751   }
 752   else {
 753     # Comma delimited lisr of specified property names...
 754     my($Mode, $PropertyName, @PropertyNames, @UnsupportedPropertyNames);
 755 
 756     $Mode = $Options{mode};
 757     $Mode =~ s/ //g;
 758 
 759     @PropertyNames = split ",", $Mode;
 760     @UnsupportedPropertyNames = ();
 761 
 762     for $PropertyName (@PropertyNames) {
 763       if (exists($OptionsInfo{SupportedPropertyNamesMap}{lc($PropertyName)})) {
 764         push @SpecifiedPropertyNames, $PropertyName;
 765       }
 766       else {
 767         push @UnsupportedPropertyNames, $PropertyName;
 768       }
 769     }
 770     if (@UnsupportedPropertyNames) {
 771       if (@UnsupportedPropertyNames > 1) {
 772         warn "Error: The physicochemical property names specified - ", JoinWords(\@UnsupportedPropertyNames, ", ", 0)," - for option \"-m --mode\" are not valid.\n";
 773       }
 774       else {
 775         warn "Error: The physicochemical property name specified, @UnsupportedPropertyNames , for option \"-m --mode\" is not valid.\n";
 776       }
 777       die "Allowed values:", JoinWords(\@{$OptionsInfo{SupportedPropertyNames}}, ", ", 0), "\n";
 778     }
 779     if (!@SpecifiedPropertyNames) {
 780       die "Error: No valid physicochemical property names specified for option \"-m --mode\".\n";
 781     }
 782   }
 783 
 784   # Set up specified property names map...
 785   PROPERTY: for $SpecifiedPropertyName (@SpecifiedPropertyNames) {
 786     if (exists $SpecifiedPropertyNamesMap{lc($SpecifiedPropertyName)}) {
 787       warn "Warning: The physicochemical property name, $SpecifiedPropertyName, is specified multiple times as value of option \"-m --mode\" .\n";
 788       next PROPERTY;
 789     }
 790     # Canonical specified property name...
 791     $SpecifiedPropertyNamesMap{lc($SpecifiedPropertyName)} = $OptionsInfo{SupportedPropertyNamesMap}{lc($SpecifiedPropertyName)};
 792   }
 793 
 794   # Make sure for calculation of  RuleOf3Violations, all appropriate property names are specified...
 795   if ($Options{ruleof3violations} =~ /^Yes$/i && $Options{mode} =~ /^RuleOf5$/i) {
 796     die "Error: The value specified, $Options{ruleof3violations}, for  \"--RuleOf3Violations\" option in \"RuleOf5\" \"-m --Mode\" is not valid. You must specify RuleOf3 value for \"-m --Mode\" to calculate RuleOf3 violations.\n";
 797   }
 798 
 799   if ($Options{ruleof3violations} =~ /^Yes$/i) {
 800     my($RuleOf3PropertyName, @MissingRuleOf3Names);
 801 
 802     @MissingRuleOf3Names = ();
 803     PROPERTY: for $RuleOf3PropertyName (@{$OptionsInfo{RuleOf3PropertyNames}}) {
 804       if (exists $SpecifiedPropertyNamesMap{lc($RuleOf3PropertyName)}) {
 805         next PROPERTY;
 806       }
 807       push @MissingRuleOf3Names, $RuleOf3PropertyName;
 808 
 809       # Add property name to specified properties names list and map...
 810       push @SpecifiedPropertyNames, $RuleOf3PropertyName;
 811       $SpecifiedPropertyNamesMap{lc($RuleOf3PropertyName)} = $OptionsInfo{SupportedPropertyNamesMap}{lc($RuleOf3PropertyName)};
 812     }
 813     if (@MissingRuleOf3Names) {
 814       warn "Warning: The following physicochemical property names not specified in \"-m --Mode\" option are required for calculating RuleOf3Violations and have been added to the list of property names: @MissingRuleOf3Names\n";
 815     }
 816   }
 817 
 818   # Make sure for calculation of  RuleOf5Violations, all appropriate property names are specified...
 819   if ($Options{ruleof5violations} =~ /^Yes$/i && $Options{mode} =~ /^RuleOf3$/i) {
 820     die "Error: The value specified, $Options{ruleof5violations}, for  \"--RuleOf5Violations\" option in \"RuleOf3\" \"-m --Mode\" is not valid. You must specify RuleOf5 value for \"-m --Mode\" to calculate RuleOf5 violations.\n";
 821   }
 822 
 823   if ($Options{ruleof5violations} =~ /^Yes$/i) {
 824     my($RuleOf5PropertyName, @MissingRuleOf5Names);
 825 
 826     @MissingRuleOf5Names = ();
 827     PROPERTY: for $RuleOf5PropertyName (@{$OptionsInfo{RuleOf5PropertyNames}}) {
 828       if (exists $SpecifiedPropertyNamesMap{lc($RuleOf5PropertyName)}) {
 829         next PROPERTY;
 830       }
 831       push @MissingRuleOf5Names, $RuleOf5PropertyName;
 832 
 833       # Add property name to specified properties names list and map...
 834       push @SpecifiedPropertyNames, $RuleOf5PropertyName;
 835       $SpecifiedPropertyNamesMap{lc($RuleOf5PropertyName)} = $OptionsInfo{SupportedPropertyNamesMap}{lc($RuleOf5PropertyName)};
 836     }
 837     if (@MissingRuleOf5Names) {
 838       warn "Warning: The following physicochemical property names not specified in \"-m --Mode\" option are required for calculating RuleOf5Violations and have been added to the list of property names: @MissingRuleOf5Names\n";
 839     }
 840   }
 841   $OptionsInfo{Mode} = $Options{mode};
 842 
 843   # Setup canonical specified property names corresponding to supported names in mixed case...
 844   my(@SpecifiedCanonicalPropertyNames);
 845 
 846   @SpecifiedCanonicalPropertyNames = ();
 847   for $SpecifiedPropertyName (@SpecifiedPropertyNames) {
 848     push @SpecifiedCanonicalPropertyNames, $SpecifiedPropertyNamesMap{lc($SpecifiedPropertyName)};
 849   }
 850   @{$OptionsInfo{SpecifiedPropertyNames}} = @SpecifiedCanonicalPropertyNames;
 851   %{$OptionsInfo{SpecifiedPropertyNamesMap}} = %SpecifiedPropertyNamesMap;
 852 
 853   # Based on specified property names, figure out whether hydrogens need to be added before
 854   # calculation of properties...
 855   #
 856   $OptionsInfo{AddHydrogens} = 0;
 857   if (exists($SpecifiedPropertyNamesMap{lc('MolecularVolume')}) || exists($SpecifiedPropertyNamesMap{lc('SLogP')}) || exists($SpecifiedPropertyNamesMap{lc('SMR')})) {
 858     $OptionsInfo{AddHydrogens} = 1;
 859   }
 860 }
 861 
 862 # Process precision option...
 863 #
 864 sub ProcessPrecisionOption {
 865   my($ParameterName, $ParameterValue, %PrecisionParametersMap, %PrecisionParameterNamesMap);
 866 
 867   %{$OptionsInfo{PrecisionParametersMap}} = ();
 868 
 869   %PrecisionParametersMap = ('WeightPrecision' => 2, 'MassPrecision' => 4);
 870   %PrecisionParameterNamesMap = ('molecularweight' => 'WeightPrecision', 'exactmass' => 'MassPrecision');
 871 
 872   if ($Options{precision}) {
 873     # Process specified values...
 874     my($Index, $SpecifiedPrecision, @SpecifiedPrecisionValuePairs);
 875 
 876     $SpecifiedPrecision = $Options{precision};
 877     $SpecifiedPrecision =~ s/ //g;
 878     @SpecifiedPrecisionValuePairs = split ",", $SpecifiedPrecision;
 879     if (@SpecifiedPrecisionValuePairs % 2) {
 880       die "Error: Invalid number of values specified using \"--Precision\" option: It must contain even number of values.\n";
 881     }
 882     for ($Index = 0; (($Index + 1) < @SpecifiedPrecisionValuePairs); $Index += 2 ) {
 883       $ParameterName = $SpecifiedPrecisionValuePairs[$Index];
 884       $ParameterValue = $SpecifiedPrecisionValuePairs[$Index + 1];
 885       if (!exists $PrecisionParameterNamesMap{lc($ParameterName)}) {
 886         die "Error: The precision parameter name specified, $ParameterName, for option \"--Precision\" is not valid.\n";
 887       }
 888       if (!IsPositiveInteger($ParameterValue)) {
 889         die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--Precision\" is not valid. Allowed values: positive integer. \n";
 890       }
 891       $ParameterName = $PrecisionParameterNamesMap{lc($ParameterName)};
 892       $PrecisionParametersMap{$ParameterName} = $ParameterValue;
 893     }
 894   }
 895   $OptionsInfo{Precision} = $Options{precision};
 896   %{$OptionsInfo{PrecisionParametersMap}} = %PrecisionParametersMap;
 897 }
 898 
 899 # Process rotatable bonds option...
 900 sub ProcessRotatableBondsOption {
 901   my($ParameterName, $ParameterValue, %RotatableBondsParametersMap, %RotatableBondsParameterNamesMap);
 902 
 903   %{$OptionsInfo{RotatableBondsParametersMap}} = ();
 904   %RotatableBondsParametersMap = ('IgnoreTerminalBonds' => 1, 'IgnoreBondsToTripleBonds' => 1, 'IgnoreAmideBonds' => 1, 'IgnoreThioamideBonds' => 1, 'IgnoreSulfonamideBonds' => 1);
 905 
 906   for $ParameterName (keys %RotatableBondsParametersMap) {
 907     $RotatableBondsParameterNamesMap{lc($ParameterName)} = $ParameterName;
 908   }
 909 
 910   if ($Options{rotatablebonds}) {
 911     # Process specified values...
 912     my($Index, $SpecifiedRotatableBonds, @SpecifiedRotatableBondsValuePairs);
 913 
 914     $SpecifiedRotatableBonds = $Options{rotatablebonds};
 915     $SpecifiedRotatableBonds =~ s/ //g;
 916     @SpecifiedRotatableBondsValuePairs = split ",", $SpecifiedRotatableBonds;
 917     if (@SpecifiedRotatableBondsValuePairs % 2) {
 918       die "Error: Invalid number of values specified using \"--RotatableBonds\" option: It must contain even number of values.\n";
 919     }
 920     for ($Index = 0; (($Index + 1) < @SpecifiedRotatableBondsValuePairs); $Index += 2 ) {
 921       $ParameterName = $SpecifiedRotatableBondsValuePairs[$Index];
 922       $ParameterValue = $SpecifiedRotatableBondsValuePairs[$Index + 1];
 923       if (!exists $RotatableBondsParameterNamesMap{lc($ParameterName)}) {
 924         die "Error: The rotatable bonds parameter name specified, $ParameterName, for option \"--RotatableBonds\" is not valid.\n";
 925       }
 926       if ($ParameterValue !~ /^(Yes|No)$/i) {
 927         die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--RotatableBonds\" is not valid. Allowed values: Yes or No. \n";
 928       }
 929       $ParameterName = $RotatableBondsParameterNamesMap{lc($ParameterName)};
 930       $ParameterValue = ($ParameterValue =~ /^Yes$/i) ? 1 : 0;
 931       $RotatableBondsParametersMap{$ParameterName} = $ParameterValue;
 932     }
 933   }
 934   $OptionsInfo{RotatableBonds} = $Options{rotatablebonds};
 935   %{$OptionsInfo{RotatableBondsParametersMap}} = %RotatableBondsParametersMap;
 936 }
 937 
 938 # Process TPSA option...
 939 #
 940 sub ProcessTPSAOption {
 941   my($ParameterName, $ParameterValue, %TPSAParametersMap, %TPSAParameterNamesMap);
 942 
 943   %{$OptionsInfo{TPSAParametersMap}} = ();
 944 
 945   %TPSAParametersMap = ('IgnorePhosphorus' => 1, 'IgnoreSulfur' => 1);
 946   for $ParameterName (keys %TPSAParametersMap) {
 947     $TPSAParameterNamesMap{lc($ParameterName)} = $ParameterName;
 948   }
 949 
 950   if ($Options{tpsa}) {
 951     # Process specified values...
 952     my($Index, $SpecifiedTPSA, @SpecifiedTPSAValuePairs);
 953 
 954     $SpecifiedTPSA = $Options{tpsa};
 955     $SpecifiedTPSA =~ s/ //g;
 956     @SpecifiedTPSAValuePairs = split ",", $SpecifiedTPSA;
 957     if (@SpecifiedTPSAValuePairs % 2) {
 958       die "Error: Invalid number of values specified using \"--TPSA\" option: It must contain even number of values.\n";
 959     }
 960     for ($Index = 0; (($Index + 1) < @SpecifiedTPSAValuePairs); $Index += 2 ) {
 961       $ParameterName = $SpecifiedTPSAValuePairs[$Index];
 962       $ParameterValue = $SpecifiedTPSAValuePairs[$Index + 1];
 963       if (!exists $TPSAParameterNamesMap{lc($ParameterName)}) {
 964         die "Error: The TPSA parameter name specified, $ParameterName, for option \"--TPSA\" is not valid.\n";
 965       }
 966       if ($ParameterValue !~ /^(Yes|No)$/i) {
 967         die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--TPSA\" is not valid. Allowed values: Yes or No. \n";
 968       }
 969       $ParameterName = $TPSAParameterNamesMap{lc($ParameterName)};
 970       $ParameterValue = ($ParameterValue =~ /^Yes$/i) ? 1 : 0;
 971       $TPSAParametersMap{$ParameterName} = $ParameterValue;
 972     }
 973   }
 974   $OptionsInfo{TPSA} = $Options{tpsa};
 975   %{$OptionsInfo{TPSAParametersMap}} = %TPSAParametersMap;
 976 }
 977 
 978 # Process molecular complexity parameters...
 979 #
 980 sub ProcessMolecularComplexityOption {
 981   my($MolecularComplexityType, $ParameterName, $ParameterValue, @ParameterNames, @ParameterValues, @AtomIdentifierTypeParameters, %ComplexityParametersMap, %ComplexityParameterNamesMap);
 982 
 983   %{$OptionsInfo{MolecularComplexityParametersMap}} = ();
 984 
 985   %ComplexityParametersMap = ('MolecularComplexityType' => '', 'AtomIdentifierType' => '',
 986                               'AtomicInvariantsToUse' => '', 'FunctionalClassesToUse' => '',
 987                               'MACCSKeysSize' => '166', 'NeighborhoodRadius' => '2',
 988                               'MinPathLength' => '1', 'MaxPathLength' => '8', 'UseBondSymbols' => '1',
 989                               'MinDistance' => '1', 'MaxDistance' => '10', 'UseTriangleInequality' => '',
 990                               'DistanceBinSize' => '2', 'NormalizationMethodology' => 'None');
 991 
 992   %ComplexityParameterNamesMap = ();
 993   for $ParameterName (keys %ComplexityParametersMap) {
 994     $ComplexityParameterNamesMap{lc($ParameterName)} = $ParameterName;
 995   }
 996 
 997   if ($Options{molecularcomplexity}) {
 998     # Process specified values...
 999     my($Index, $SpecifiedComplexity, @SpecifiedComplexityValuePairs);
1000 
1001     $SpecifiedComplexity = $Options{molecularcomplexity};
1002 
1003     @SpecifiedComplexityValuePairs = split ",", $SpecifiedComplexity;
1004     if (@SpecifiedComplexityValuePairs % 2) {
1005       die "Error: Invalid number of values specified using \"--MolecularComplexity\" option: It must contain even number of values.\n";
1006     }
1007 
1008     for ($Index = 0; (($Index + 1) < @SpecifiedComplexityValuePairs); $Index += 2 ) {
1009       $ParameterName = $SpecifiedComplexityValuePairs[$Index];
1010       $ParameterValue = $SpecifiedComplexityValuePairs[$Index + 1];
1011 
1012       $ParameterName = RemoveLeadingAndTrailingWhiteSpaces($ParameterName);
1013       $ParameterValue = RemoveLeadingAndTrailingWhiteSpaces($ParameterValue);
1014 
1015       if (!exists $ComplexityParameterNamesMap{lc($ParameterName)}) {
1016         die "Error: The molecular complexity parameter name specified, $ParameterName, for option \"--MolecularComplexity\" is not valid.\n";
1017       }
1018       $ParameterName = $ComplexityParameterNamesMap{lc($ParameterName)};
1019 
1020       if ($ParameterName =~ /^AtomicInvariantsToUse$/i) {
1021         my($AtomSymbolFound);
1022 
1023         $AtomSymbolFound = 0;
1024         @ParameterValues = split(' ', $ParameterValue);
1025         for $ParameterValue (@ParameterValues) {
1026           if (!AtomTypes::AtomicInvariantsAtomTypes::IsAtomicInvariantAvailable($ParameterValue)) {
1027             die "Error: The atomic invariant specified, $ParameterValue, for  AtomicInvariantsToUse in option \"--MolecularComplexity\" is not valid.\n";
1028           }
1029           if ($ParameterValue =~ /^(AS|AtomSymbol)$/i) {
1030             $AtomSymbolFound = 1;
1031           }
1032         }
1033         if (!$AtomSymbolFound) {
1034           die "Error: The atomic invariants specified using AtomicInvariantsToUse in option \"--MolecularComplexity\" is not valid: AtomicInvariant atom symbol, AS or AtomSymbol, must be specified.\n";
1035         }
1036         $ParameterValue = JoinWords(\@ParameterValues, ",", 0);
1037       }
1038       elsif ($ParameterName =~ /^FunctionalClassesToUse$/i) {
1039         @ParameterValues = split(' ', $ParameterValue);
1040         for $ParameterValue (@ParameterValues) {
1041           if (!AtomTypes::FunctionalClassAtomTypes::IsFunctionalClassAvailable($ParameterValue)) {
1042             die "Error: The functional class specified, $ParameterValue, for  FunctionalClassesToUse in option \"--MolecularComplexity\" is not valid.\n";
1043           }
1044         }
1045         $ParameterValue = JoinWords(\@ParameterValues, ",", 0);
1046       }
1047       else {
1048         if ($ParameterValue =~ / /) {
1049           $ParameterValue =~ s/ //g;
1050         }
1051         if ($ParameterValue =~ /^(Yes|No)$/i) {
1052           $ParameterValue = ($ParameterValue =~ /^Yes$/i) ? 1 : 0;
1053         }
1054       }
1055 
1056       if ($ParameterName =~ /^MolecularComplexityType$/i) {
1057         if ($ParameterValue !~ /^(AtomTypesFingerprints|ExtendedConnectivityFingerprints|MACCSKeys|PathLengthFingerprints|TopologicalAtomPairsFingerprints|TopologicalAtomTripletsFingerprints|TopologicalAtomTorsionsFingerprints|TopologicalPharmacophoreAtomPairsFingerprints|TopologicalPharmacophoreAtomTripletsFingerprints)$/i) {
1058           die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--MolecularComplexity\" is not valid. Allowed values: AtomTypesFingerprints, ExtendedConnectivityFingerprints, MACCSKeys, PathLengthFingerprints, TopologicalAtomPairsFingerprints, TopologicalAtomTripletsFingerprints, TopologicalAtomTorsionsFingerprints, TopologicalPharmacophoreAtomPairsFingerprints, or TopologicalPharmacophoreAtomTripletsFingerprints..\n";
1059         }
1060       }
1061       elsif ($ParameterName =~ /^AtomIdentifierType$/i) {
1062         if ($ParameterValue !~ /^(AtomicInvariantsAtomTypes|FunctionalClassAtomTypes|DREIDINGAtomTypes|EStateAtomTypes|MMFF94AtomTypes|SLogPAtomTypes|SYBYLAtomTypes|TPSAAtomTypes|UFFAtomTypes)$/i) {
1063           die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--MolecularComplexity\" is not valid. Supported atom identifier types in current release of MayaChemTools: AtomicInvariantsAtomTypes, FunctionalClassAtomTypes, DREIDINGAtomTypes, EStateAtomTypes, MMFF94AtomTypes, SLogPAtomTypes, SYBYLAtomTypes, TPSAAtomTypes and UFFAtomTypes.\n";
1064         }
1065       }
1066       elsif ($ParameterName =~ /^(MACCSKeysSize|MinPathLength|MaxPathLength|MinDistance|MaxDistance|DistanceBinSize)$/i) {
1067         if (!IsPositiveInteger($ParameterValue)) {
1068           die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--MolecularComplexity\" is not valid. Allowed values: positive integer. \n";
1069         }
1070       }
1071       elsif ($ParameterName =~ /^NeighborhoodRadius$/i) {
1072         if (!(IsInteger($ParameterValue) && $ParameterValue >=0)) {
1073           die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--MolecularComplexity\" is not valid. Allowed values: 0 or positive integer. \n";
1074         }
1075       }
1076       elsif ($ParameterName =~ /^NormalizationMethodology$/i) {
1077         if ($ParameterValue !~ /^(None|ByHeavyAtomsCount|ByPossibleKeysCount)$/i) {
1078           die "Error: The parameter value specified, $ParameterValue, for parameter name, $ParameterName in option \"--MolecularComplexity\" is not valid. Allowed values: None, ByHeavyAtomsCount, or ByPossibleKeysCount\n";
1079         }
1080       }
1081       $ComplexityParametersMap{$ParameterName} = $ParameterValue;
1082     }
1083 
1084     if ($ComplexityParametersMap{MACCSKeysSize} !~ /^(166|322)$/i) {
1085       die "Error: The parameter value specified, $ComplexityParametersMap{MACCSKeysSize}, for parameter name, MACCSKeysSize in option \"--MolecularComplexity\" is not valid. Allowed values: 166 or 322\n";
1086     }
1087     if ($ComplexityParametersMap{MinPathLength} > $ComplexityParametersMap{MaxPathLength}) {
1088       die "Error: The parameter value specified for MinPathLength, $ComplexityParametersMap{MinPathLength}, must be <= MaxPathLength, $ComplexityParametersMap{MaxPathLength} ...\n";
1089     }
1090     if ($ComplexityParametersMap{MinDistance} > $ComplexityParametersMap{MaxDistance}) {
1091       die "Error: The parameter value specified for MinDistance, $ComplexityParametersMap{MinDistance}, must be <= MaxDistance, $ComplexityParametersMap{MaxDistance} ...\n";
1092     }
1093   }
1094 
1095   # Set default parameter values...
1096 
1097   if (IsEmpty($ComplexityParametersMap{MolecularComplexityType})) {
1098     $ComplexityParametersMap{MolecularComplexityType} = 'MACCSKeys';
1099   }
1100   $MolecularComplexityType = $ComplexityParametersMap{MolecularComplexityType};
1101 
1102 
1103   if (IsEmpty($ComplexityParametersMap{AtomIdentifierType})) {
1104     $ComplexityParametersMap{AtomIdentifierType} = ($MolecularComplexityType =~ /^(TopologicalPharmacophoreAtomPairsFingerprints|TopologicalPharmacophoreAtomTripletsFingerprints)$/i) ? "FunctionalClassAtomTypes" : "AtomicInvariantsAtomTypes";
1105   }
1106 
1107   if (IsEmpty($ComplexityParametersMap{AtomicInvariantsToUse})) {
1108     my($AtomicInvariantsToUse);
1109 
1110     if ($MolecularComplexityType =~ /^(AtomTypesFingerprints|TopologicalAtomPairsFingerprints|TopologicalAtomTripletsFingerprints|TopologicalAtomTorsionsFingerprints)$/i) {
1111       $AtomicInvariantsToUse = "AS,X,BO,H,FC";
1112     }
1113     elsif ($MolecularComplexityType =~ /^ExtendedConnectivityFingerprints$/i) {
1114       $AtomicInvariantsToUse = "AS,X,BO,H,FC,MN";
1115     }
1116     else {
1117       $AtomicInvariantsToUse = "AS";
1118     }
1119     $ComplexityParametersMap{AtomicInvariantsToUse} = $AtomicInvariantsToUse;
1120   }
1121 
1122   if (IsEmpty($ComplexityParametersMap{FunctionalClassesToUse})) {
1123     my($FunctionalClassesToUse);
1124 
1125     if ($MolecularComplexityType =~ /^TopologicalPharmacophoreAtomPairsFingerprints$/i) {
1126       $FunctionalClassesToUse = "HBD,HBA,PI,NI,H";
1127     }
1128     elsif ($MolecularComplexityType =~ /^TopologicalPharmacophoreAtomTripletsFingerprints$/i) {
1129       $FunctionalClassesToUse = "HBD,HBA,PI,NI,H,Ar";
1130     }
1131     else {
1132       $FunctionalClassesToUse = "HBD,HBA,PI,NI,H,Ar,Hal";
1133     }
1134     $ComplexityParametersMap{FunctionalClassesToUse} = $FunctionalClassesToUse;
1135   }
1136 
1137   my(@AtomicInvariantsToUse);
1138   @AtomicInvariantsToUse = split ',', $ComplexityParametersMap{AtomicInvariantsToUse};
1139   $ComplexityParametersMap{AtomicInvariantsToUse} = \@AtomicInvariantsToUse;
1140 
1141   my(@FunctionalClassesToUse);
1142   @FunctionalClassesToUse = split ',', $ComplexityParametersMap{FunctionalClassesToUse};
1143   $ComplexityParametersMap{FunctionalClassesToUse} = \@FunctionalClassesToUse;
1144 
1145   if (IsEmpty($ComplexityParametersMap{UseTriangleInequality})) {
1146     $ComplexityParametersMap{UseTriangleInequality} = 0;
1147     if ($MolecularComplexityType =~ /^TopologicalPharmacophoreAtomTripletsFingerprints$/i) {
1148       $ComplexityParametersMap{UseTriangleInequality} = 1;
1149     }
1150   }
1151 
1152   if ($MolecularComplexityType =~ /^(TopologicalPharmacophoreAtomPairsFingerprints|TopologicalPharmacophoreAtomTripletsFingerprints)$/i) {
1153     if ($ComplexityParametersMap{AtomIdentifierType} !~ /^FunctionalClassAtomTypes$/i) {
1154       die "Error: The parameter value specified for AtomIdentifierType, $ComplexityParametersMap{AtomIdentifierType}, in option \"--MolecularComplexity\" is not valid for MolecularComplexityType, $MolecularComplexityType: Allowed value: FunctionalClassAtomTypes...\n";
1155     }
1156   }
1157 
1158   # Set up approprate paremeter names for specified molecular complexity...
1159 
1160   @ParameterNames = ();
1161   push @ParameterNames, 'MolecularComplexityType';
1162 
1163   @AtomIdentifierTypeParameters = ();
1164   push @AtomIdentifierTypeParameters, 'AtomIdentifierType';
1165   if ($ComplexityParametersMap{AtomIdentifierType} =~ /^AtomicInvariantsAtomTypes$/i) {
1166     push @AtomIdentifierTypeParameters, 'AtomicInvariantsToUse';
1167   }
1168   elsif ($ComplexityParametersMap{AtomIdentifierType} =~ /^FunctionalClassAtomTypes$/i) {
1169     push @AtomIdentifierTypeParameters, 'FunctionalClassesToUse';
1170   }
1171 
1172   COMPLEXITYTYPE: {
1173     if ($MolecularComplexityType =~ /^AtomTypesFingerprints$/i) {
1174       push @ParameterNames, @AtomIdentifierTypeParameters;
1175       last COMPLEXITYTYPE;
1176     }
1177     if ($MolecularComplexityType =~ /^ExtendedConnectivityFingerprints$/i) {
1178       push @ParameterNames, @AtomIdentifierTypeParameters;
1179       push @ParameterNames, ('NeighborhoodRadius', 'NormalizationMethodology');
1180       last COMPLEXITYTYPE;
1181     }
1182     if ($MolecularComplexityType =~ /^MACCSKeys$/i) {
1183       push @ParameterNames, 'MACCSKeysSize';
1184       last COMPLEXITYTYPE;
1185     }
1186     if ($MolecularComplexityType =~ /^PathLengthFingerprints$/i) {
1187       push @ParameterNames, @AtomIdentifierTypeParameters;
1188       push @ParameterNames, ('MinPathLength', 'MaxPathLength', 'UseBondSymbols');
1189       last COMPLEXITYTYPE;
1190     }
1191     if ($MolecularComplexityType =~ /^TopologicalAtomPairsFingerprints$/i) {
1192       push @ParameterNames, @AtomIdentifierTypeParameters;
1193       push @ParameterNames, ('MinDistance', 'MaxDistance');
1194       last COMPLEXITYTYPE;
1195     }
1196     if ($MolecularComplexityType =~ /^TopologicalAtomTripletsFingerprints$/i) {
1197       push @ParameterNames, @AtomIdentifierTypeParameters;
1198       push @ParameterNames, ('MinDistance', 'MaxDistance', 'UseTriangleInequality');
1199       last COMPLEXITYTYPE;
1200     }
1201     if ($MolecularComplexityType =~ /^TopologicalAtomTorsionsFingerprints$/i) {
1202       push @ParameterNames, @AtomIdentifierTypeParameters;
1203       last COMPLEXITYTYPE;
1204     }
1205     if ($MolecularComplexityType =~ /^TopologicalPharmacophoreAtomPairsFingerprints$/i) {
1206       push @ParameterNames, ('AtomIdentifierType', 'FunctionalClassesToUse', 'MinDistance', 'MaxDistance', 'NormalizationMethodology');
1207       last COMPLEXITYTYPE;
1208     }
1209     if ($MolecularComplexityType =~ /^TopologicalPharmacophoreAtomTripletsFingerprints$/i) {
1210       push @ParameterNames, ('AtomIdentifierType', 'FunctionalClassesToUse', 'MinDistance', 'MaxDistance', 'UseTriangleInequality', 'NormalizationMethodology', 'DistanceBinSize');
1211       last COMPLEXITYTYPE;
1212     }
1213     die "Error: The parameter value specified, $ParameterValue, for parameter name MolecularComplexityType using \"--MolecularComplexity\" is not valid.\n";
1214   }
1215 
1216   $OptionsInfo{MolecularComplexity} = $Options{molecularcomplexity};
1217 
1218   %{$OptionsInfo{MolecularComplexityParametersMap}} = ();
1219   for $ParameterName (@ParameterNames) {
1220     $ParameterValue = $ComplexityParametersMap{$ParameterName};
1221     $OptionsInfo{MolecularComplexityParametersMap}{$ParameterName} = $ParameterValue;
1222   }
1223 }
1224 
1225 # Setup script usage  and retrieve command line arguments specified using various options...
1226 sub SetupScriptUsage {
1227 
1228   # Retrieve all the options...
1229   %Options = ();
1230 
1231   $Options{aromaticitymodel} = 'MayaChemToolsAromaticityModel';
1232 
1233   $Options{compoundidmode} = 'LabelPrefix';
1234   $Options{compoundidlabel} = 'CompoundID';
1235   $Options{datafieldsmode} = 'CompoundID';
1236 
1237   $Options{filter} = 'Yes';
1238 
1239   $Options{hydrogenbonds} = 'HBondsType2';
1240 
1241   $Options{keeplargestcomponent} = 'Yes';
1242 
1243   # Default mode values are set later...
1244   $Options{mode} = '';
1245 
1246   # Default moelcular complexity values are set later...
1247   $Options{molecularcomplexity} = '';
1248 
1249   # Default precision values are set later...
1250   $Options{precision} = '';
1251 
1252   $Options{output} = 'text';
1253   $Options{outdelim} = 'comma';
1254   $Options{quote} = 'yes';
1255 
1256   # Default rotatable bond parameter values are set later...
1257   $Options{rotatablebonds} = '';
1258 
1259   $Options{ruleof3violations} = 'No';
1260   $Options{ruleof5violations} = 'No';
1261 
1262   # Default TPSA paramater values are set later...
1263   $Options{tpsa} = '';
1264 
1265   if (!GetOptions(\%Options, "aromaticitymodel=s", "compoundid=s", "compoundidlabel=s", "compoundidmode=s", "datafields=s", "datafieldsmode|d=s", "filter|f=s", "help|h", "hydrogenbonds=s", "keeplargestcomponent|k=s", "mode|m=s", "molecularcomplexity=s", "outdelim=s", "output=s", "overwrite|o", "precision=s", "rotatablebonds=s", "ruleof3violations=s", "ruleof5violations=s", "quote|q=s", "root|r=s", "tpsa=s", "workingdir|w=s")) {
1266     die "\nTo get a list of valid options and their values, use \"$ScriptName -h\" or\n\"perl -S $ScriptName -h\" command and try again...\n";
1267   }
1268   if ($Options{workingdir}) {
1269     if (! -d $Options{workingdir}) {
1270       die "Error: The value specified, $Options{workingdir}, for option \"-w --workingdir\" is not a directory name.\n";
1271     }
1272     chdir $Options{workingdir} or die "Error: Couldn't chdir $Options{workingdir}: $! \n";
1273   }
1274   if (!Molecule::IsSupportedAromaticityModel($Options{aromaticitymodel})) {
1275     my(@SupportedModels) = Molecule::GetSupportedAromaticityModels();
1276     die "Error: The value specified, $Options{aromaticitymodel}, for option \"--AromaticityModel\" is not valid. Supported aromaticity models in current release of MayaChemTools: @SupportedModels\n";
1277   }
1278   if ($Options{compoundidmode} !~ /^(DataField|MolName|LabelPrefix|MolNameOrLabelPrefix)$/i) {
1279     die "Error: The value specified, $Options{compoundidmode}, for option \"--CompoundIDMode\" is not valid. Allowed values: DataField, MolName, LabelPrefix or MolNameOrLabelPrefix\n";
1280   }
1281   if ($Options{datafieldsmode} !~ /^(All|Common|Specify|CompoundID)$/i) {
1282     die "Error: The value specified, $Options{datafieldsmode}, for option \"-d, --DataFieldsMode\" is not valid. Allowed values: All, Common, Specify or CompoundID\n";
1283   }
1284   if ($Options{filter} !~ /^(Yes|No)$/i) {
1285     die "Error: The value specified, $Options{filter}, for option \"-f, --Filter\" is not valid. Allowed values: Yes or No\n";
1286   }
1287   if ($Options{hydrogenbonds} !~ /^(HBondsType1|HydrogenBondsType1|HBondsType2|HydrogenBondsType2)$/i) {
1288     die "Error: The value specified, $Options{hydrogenbonds}, for option \"--HydrogenBonds\" is not valid. Allowed values: HBondsType1, HydrogenBondsType1, HBondsType2, HydrogenBondsType2\n";
1289   }
1290   if ($Options{keeplargestcomponent} !~ /^(Yes|No)$/i) {
1291     die "Error: The value specified, $Options{keeplargestcomponent}, for option \"-k, --KeepLargestComponent\" is not valid. Allowed values: Yes or No\n";
1292   }
1293   if ($Options{output} !~ /^(SD|text|both)$/i) {
1294     die "Error: The value specified, $Options{output}, for option \"--output\" is not valid. Allowed values: SD, text, or both\n";
1295   }
1296   if ($Options{outdelim} !~ /^(comma|semicolon|tab)$/i) {
1297     die "Error: The value specified, $Options{outdelim}, for option \"--outdelim\" is not valid. Allowed values: comma, tab, or semicolon\n";
1298   }
1299   if ($Options{quote} !~ /^(Yes|No)$/i) {
1300     die "Error: The value specified, $Options{quote}, for option \"-q --quote\" is not valid. Allowed values: Yes or No\n";
1301   }
1302   if ($Options{ruleof3violations} !~ /^(Yes|No)$/i) {
1303     die "Error: The value specified, $Options{ruleof3violations}, for option \"--RuleOf3Violations\" is not valid. Allowed values: Yes or No\n";
1304   }
1305   if ($Options{ruleof5violations} !~ /^(Yes|No)$/i) {
1306     die "Error: The value specified, $Options{ruleof5violations}, for option \"--RuleOf5Violations\" is not valid. Allowed values: Yes or No\n";
1307   }
1308 }
1309