MayaChemTools

   1 package PeriodicTable;
   2 #
   3 # $RCSfile: PeriodicTable.pm,v $
   4 # $Date: 2015/02/28 20:47:18 $
   5 # $Revision: 1.37 $
   6 #
   7 # Author: Manish Sud <msud@san.rr.com>
   8 #
   9 # Copyright (C) 2015 Manish Sud. All rights reserved.
  10 #
  11 # This file is part of MayaChemTools.
  12 #
  13 # MayaChemTools is free software; you can redistribute it and/or modify it under
  14 # the terms of the GNU Lesser General Public License as published by the Free
  15 # Software Foundation; either version 3 of the License, or (at your option) any
  16 # later version.
  17 #
  18 # MayaChemTools is distributed in the hope that it will be useful, but without
  19 # any warranty; without even the implied warranty of merchantability of fitness
  20 # for a particular purpose.  See the GNU Lesser General Public License for more
  21 # details.
  22 #
  23 # You should have received a copy of the GNU Lesser General Public License
  24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
  25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
  26 # Boston, MA, 02111-1307, USA.
  27 #
  28 
  29 use strict;
  30 use Carp;
  31 use Text::ParseWords;
  32 use TextUtil;
  33 use FileUtil;
  34 
  35 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  36 
  37 @ISA = qw(Exporter);
  38 @EXPORT = qw();
  39 @EXPORT_OK = qw(GetElements GetElementsByGroupName GetElementsByGroupNumber GetElementsByAmericanStyleGroupLabel GetElementsByEuropeanStyleGroupLabel GetElementsByPeriodNumber GetElementMostAbundantNaturalIsotopeData GetElementNaturalIsotopeCount GetElementNaturalIsotopesData GetElementNaturalIsotopeAbundance GetElementMostAbundantNaturalIsotopeMass GetElementMostAbundantNaturalIsotopeMassNumber GetElementNaturalIsotopeMass GetElementPropertiesData GetElementPropertiesNames GetElementPropertiesNamesAndUnits GetElementPropertyUnits GetIUPACGroupNumberFromAmericanStyleGroupLabel GetIUPACGroupNumberFromEuropeanStyleGroupLabel IsElement IsElementNaturalIsotopeMassNumber IsElementProperty);
  40 
  41 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  42 
  43 #
  44 # Load atomic properties and isotope data for elements...
  45 #
  46 my(%ElementDataMap, %ElementIsotopeDataMap, %ElementSymbolMap, @ElementPropertyNames, %ElementPropertyNamesMap, %ElementIsotopeDerivedDataMap);
  47 _LoadPeriodicTableElementData();
  48 
  49 #
  50 # Get a list of all known element symbols...
  51 #
  52 sub GetElements {
  53   my($AtomicNumber, @ElementSymbols);
  54 
  55   @ElementSymbols = ();
  56   for $AtomicNumber (sort {$a <=> $b} keys %ElementDataMap) {
  57       push @ElementSymbols, $ElementDataMap{$AtomicNumber}{ElementSymbol};
  58   }
  59   return (wantarray ? @ElementSymbols : \@ElementSymbols);
  60 }
  61 
  62 #
  63 # Get element symbols of elements with a specific group name. Valid group
  64 # names are: Alkali metals, Alkaline earth metals, Coinage metals, Pnictogens,
  65 # Chalcogens, Halogens, Noble gases; Additionally, usage of Lanthanides (Lanthanoids)
  66 # and Actinides (Actinoids) is also supported.
  67 #
  68 sub GetElementsByGroupName {
  69   my($SpecifiedGroupName) = @_;
  70   my($AtomicNumber, @ElementSymbols, $GroupName);
  71 
  72   if (IsEmpty($SpecifiedGroupName)) {
  73     return (wantarray ? () : undef);
  74   }
  75   if ($SpecifiedGroupName =~ /Lanthanide/i) {
  76     $SpecifiedGroupName = 'Lanthanoids';
  77   }
  78   elsif ($SpecifiedGroupName =~ /Actinide/i) {
  79     $SpecifiedGroupName = 'Actinoids';
  80   }
  81   @ElementSymbols = ();
  82   for $AtomicNumber (sort {$a <=> $b} keys %ElementDataMap) {
  83     $GroupName = $ElementDataMap{$AtomicNumber}{GroupName};
  84     if ($SpecifiedGroupName =~ /$GroupName/i ) {
  85       push @ElementSymbols, $ElementDataMap{$AtomicNumber}{ElementSymbol};
  86     }
  87   }
  88   return (wantarray ? @ElementSymbols : \@ElementSymbols);
  89 }
  90 
  91 #
  92 # Get element symbols of elements in a specific IUPAC group number.
  93 # A reference to an array containing element symbols is returned.
  94 #
  95 sub GetElementsByGroupNumber {
  96   my($GroupNumber) = @_;
  97   my($AtomicNumber, @ElementSymbols);
  98 
  99   if (!IsInteger($GroupNumber)) {
 100     return (wantarray ? () : undef);
 101   }
 102 
 103   @ElementSymbols = ();
 104   for $AtomicNumber (sort {$a <=> $b} keys %ElementDataMap) {
 105     if ($GroupNumber eq $ElementDataMap{$AtomicNumber}{GroupNumber}) {
 106       push @ElementSymbols, $ElementDataMap{$AtomicNumber}{ElementSymbol};
 107     }
 108   }
 109   return (wantarray ? @ElementSymbols : \@ElementSymbols);
 110 }
 111 
 112 #
 113 # Get element symbols of elements in a specific American style group label.
 114 # A reference to an array containing element symbols is returned.
 115 #
 116 sub GetElementsByAmericanStyleGroupLabel {
 117   my($GroupLabel) = @_;
 118 
 119   return _GetElementsByGroupLabel($GroupLabel, 'AmericanStyle');
 120 }
 121 
 122 #
 123 # Get element symbols of elements in a specific European style group label.
 124 # A reference to an array containing element symbols is returned.
 125 #
 126 sub GetElementsByEuropeanStyleGroupLabel {
 127   my($GroupLabel) = @_;
 128 
 129   return _GetElementsByGroupLabel($GroupLabel, 'EuropeanStyle');
 130 }
 131 
 132 #
 133 # Get IUPAC group number from American style group label. A comma delimited
 134 # string is returned for group VIII or VIIIB.
 135 #
 136 sub GetIUPACGroupNumberFromAmericanStyleGroupLabel {
 137   my($GroupLabel) = @_;
 138   my($GroupNumber);
 139 
 140   if (IsEmpty($GroupLabel)) {
 141     return undef;
 142   }
 143   $GroupNumber = "";
 144   SWITCH: {
 145       if ($GroupLabel =~ /^IA$/) { $GroupNumber = 1; last SWITCH;}
 146       if ($GroupLabel =~ /^IIA$/) { $GroupNumber = 2; last SWITCH;}
 147       if ($GroupLabel =~ /^IIIB$/) { $GroupNumber = 3; last SWITCH;}
 148       if ($GroupLabel =~ /^IVB$/) { $GroupNumber = 4; last SWITCH;}
 149       if ($GroupLabel =~ /^VB$/) { $GroupNumber = 5; last SWITCH;}
 150       if ($GroupLabel =~ /^VIB$/) { $GroupNumber = 6; last SWITCH;}
 151       if ($GroupLabel =~ /^VIIB$/) { $GroupNumber = 7; last SWITCH;}
 152       if ($GroupLabel =~ /^(VIII|VIIIB)$/) { $GroupNumber = '8,9,10'; last SWITCH;}
 153       if ($GroupLabel =~ /^IB$/) { $GroupNumber = 11; last SWITCH;}
 154       if ($GroupLabel =~ /^IIB$/) { $GroupNumber = 12; last SWITCH;}
 155       if ($GroupLabel =~ /^IIIA$/) { $GroupNumber = 13; last SWITCH;}
 156       if ($GroupLabel =~ /^IVA$/) { $GroupNumber = 14; last SWITCH;}
 157       if ($GroupLabel =~ /^VA$/) { $GroupNumber = 15; last SWITCH;}
 158       if ($GroupLabel =~ /^VIA$/) { $GroupNumber = 16; last SWITCH;}
 159       if ($GroupLabel =~ /^VIIA$/) { $GroupNumber = 17; last SWITCH;}
 160       if ($GroupLabel =~ /^VIIIA$/) { $GroupNumber = 18; last SWITCH;}
 161       $GroupNumber = "";
 162   }
 163   if (!$GroupNumber) {
 164     return undef;
 165   }
 166   return $GroupNumber;
 167 }
 168 
 169 #
 170 # Get IUPAC group number from European style group label. A comma delimited
 171 # string is returned for group VIII or VIIIA
 172 #
 173 sub GetIUPACGroupNumberFromEuropeanStyleGroupLabel {
 174   my($GroupLabel) = @_;
 175   my($GroupNumber);
 176 
 177   if (IsEmpty($GroupLabel)) {
 178     return undef;
 179   }
 180   $GroupNumber = "";
 181   SWITCH: {
 182       if ($GroupLabel =~ /^IA$/) { $GroupNumber = 1; last SWITCH;}
 183       if ($GroupLabel =~ /^IIA$/) { $GroupNumber = 2; last SWITCH;}
 184       if ($GroupLabel =~ /^IIIA$/) { $GroupNumber = 3; last SWITCH;}
 185       if ($GroupLabel =~ /^IVA$/) { $GroupNumber = 4; last SWITCH;}
 186       if ($GroupLabel =~ /^VA$/) { $GroupNumber = 5; last SWITCH;}
 187       if ($GroupLabel =~ /^VIA$/) { $GroupNumber = 6; last SWITCH;}
 188       if ($GroupLabel =~ /^VIIA$/) { $GroupNumber = 7; last SWITCH;}
 189       if ($GroupLabel =~ /^(VIII|VIIIA)$/) { $GroupNumber = '8,9,10'; last SWITCH;}
 190       if ($GroupLabel =~ /^IB$/) { $GroupNumber = 11; last SWITCH;}
 191       if ($GroupLabel =~ /^IIB$/) { $GroupNumber = 12; last SWITCH;}
 192       if ($GroupLabel =~ /^IIIB$/) { $GroupNumber = 13; last SWITCH;}
 193       if ($GroupLabel =~ /^IVB$/) { $GroupNumber = 14; last SWITCH;}
 194       if ($GroupLabel =~ /^VB$/) { $GroupNumber = 15; last SWITCH;}
 195       if ($GroupLabel =~ /^VIB$/) { $GroupNumber = 16; last SWITCH;}
 196       if ($GroupLabel =~ /^VIIB$/) { $GroupNumber = 17; last SWITCH;}
 197       if ($GroupLabel =~ /^VIIIB$/) { $GroupNumber = 18; last SWITCH;}
 198       $GroupNumber = "";
 199   }
 200   if (!$GroupNumber) {
 201     return undef;
 202   }
 203   return $GroupNumber;
 204 }
 205 
 206 #
 207 # Get element symbols of elements in a specific period number.
 208 # A reference to an array containing element symbols is returned.
 209 #
 210 sub GetElementsByPeriodNumber {
 211   my($SpecifiedPeriodNumber) = @_;
 212   my($AtomicNumber, $PeriodNumber, @ElementSymbols);
 213 
 214   if (!IsInteger($SpecifiedPeriodNumber)) {
 215     return (wantarray ? () : undef);
 216   }
 217 
 218   @ElementSymbols = ();
 219   for $AtomicNumber (sort {$a <=> $b} keys %ElementDataMap) {
 220     $PeriodNumber = $ElementDataMap{$AtomicNumber}{PeriodNumber};
 221     if ($PeriodNumber =~ /\(/) {
 222       # Lanthanides and Actinides...
 223       ($PeriodNumber) = split /\(/, $PeriodNumber;
 224     }
 225     if ($PeriodNumber == $SpecifiedPeriodNumber) {
 226       push @ElementSymbols, $ElementDataMap{$AtomicNumber}{ElementSymbol};
 227     }
 228   }
 229   return (wantarray ? @ElementSymbols : \@ElementSymbols);
 230 }
 231 
 232 #
 233 # Get data for most abundant isotope of an element using element symbol or atomic number.
 234 #
 235 sub GetElementMostAbundantNaturalIsotopeData {
 236   my($ElementID) = @_;
 237   my($AtomicNumber);
 238 
 239   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 240     return (wantarray ? () : undef);
 241   }
 242 
 243   my(@IsotopeData, $IsotopeSymbol, $MassNumber, $RelativeAtomicMass, $NaturalAbundance);
 244   $MassNumber = $ElementIsotopeDerivedDataMap{$AtomicNumber}{MostAbundantMassNumber};
 245   $IsotopeSymbol = $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{IsotopeSymbol};
 246   $RelativeAtomicMass = $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{RelativeAtomicMass};
 247   $NaturalAbundance = $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{NaturalAbundance};
 248   @IsotopeData = ();
 249   @IsotopeData = ($AtomicNumber, $IsotopeSymbol, $MassNumber, $RelativeAtomicMass, $NaturalAbundance);
 250 
 251   return (wantarray ? @IsotopeData : \@IsotopeData);
 252 
 253 }
 254 #
 255 # Get natural isotope count for an element...
 256 #
 257 sub GetElementNaturalIsotopeCount {
 258   my($ElementID) = @_;
 259   my($AtomicNumber);
 260 
 261   if ($AtomicNumber = _ValidateElementID($ElementID)) {
 262     return $ElementIsotopeDerivedDataMap{$AtomicNumber}{IsotopeCount};
 263   }
 264   else {
 265     return undef;
 266   }
 267 }
 268 
 269 #
 270 # Get all available isotope data for an element using element symbol or atomic number or
 271 # data for a specific mass number using one of these two invocation methods:
 272 #
 273 # $HashRef = GetElementNaturalIsotopesData($ElementID);
 274 #
 275 # $HashRef = GetElementNaturalIsotopesData($ElementID, $MassNumber);
 276 #
 277 # In the first mode, a reference to a two-dimensional hash array is return where first
 278 # and second dimension keys correspond to mass number and isotope data labels.
 279 #
 280 # And in the second mode, a refernce to one-dimensional hash array is returned with
 281 # keys and values corresponding to isotope data label and values.
 282 #
 283 sub GetElementNaturalIsotopesData {
 284   my($ElementID, $MassNumber, $InvocationMode, $AtomicNumber);
 285 
 286   if (@_ == 2) {
 287     ($ElementID, $MassNumber) = @_;
 288     $InvocationMode = 2;
 289   }
 290   else {
 291     ($ElementID) = @_;
 292     $InvocationMode = 1;
 293   }
 294   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 295     return undef;
 296   }
 297   if ($InvocationMode == 1) {
 298     return \%{$ElementIsotopeDataMap{$AtomicNumber}};
 299   }
 300   elsif ($InvocationMode == 2) {
 301     if (exists $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}) {
 302       return \%{$ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}};
 303     }
 304     else {
 305       return undef;
 306     }
 307   }
 308   else {
 309     return undef;
 310   }
 311 }
 312 
 313 #
 314 # Get relative atomic mass for an element with specfic mass number.
 315 #
 316 sub GetElementNaturalIsotopeMass {
 317   my($ElementID, $MassNumber) = @_;
 318   my($AtomicNumber);
 319 
 320   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 321     return undef;
 322   }
 323   if (exists $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}) {
 324     return $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{RelativeAtomicMass};
 325   }
 326   else {
 327     return undef;
 328   }
 329 }
 330 
 331 #
 332 # Get relative atomic mass of most abundant isotope for an element...
 333 #
 334 sub GetElementMostAbundantNaturalIsotopeMass {
 335   my($ElementID) = @_;
 336   my($AtomicNumber);
 337 
 338   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 339     return undef;
 340   }
 341   my($MassNumber, $RelativeAtomicMass);
 342 
 343   $MassNumber = $ElementIsotopeDerivedDataMap{$AtomicNumber}{MostAbundantMassNumber};
 344   $RelativeAtomicMass = $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{RelativeAtomicMass};
 345 
 346   return $RelativeAtomicMass;
 347 }
 348 
 349 #
 350 # Get mass number of most abundant isotope for an element...
 351 #
 352 sub GetElementMostAbundantNaturalIsotopeMassNumber {
 353   my($ElementID) = @_;
 354   my($AtomicNumber);
 355 
 356   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 357     return undef;
 358   }
 359   my($MassNumber);
 360 
 361   $MassNumber = $ElementIsotopeDerivedDataMap{$AtomicNumber}{MostAbundantMassNumber};
 362 
 363   return $MassNumber;
 364 }
 365 #
 366 # Get % natural abundance of natural isotope for an element with specfic mass number.
 367 #
 368 sub GetElementNaturalIsotopeAbundance {
 369   my($ElementID, $MassNumber) = @_;
 370   my($AtomicNumber);
 371 
 372   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 373     return undef;
 374   }
 375   if (exists $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}) {
 376     return $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{NaturalAbundance};
 377   }
 378   else {
 379     return undef;
 380   }
 381 }
 382 
 383 #
 384 # Get all available properties data for an element using element symbol or atomic number.
 385 # A reference to a hash array is returned with keys and values representing property
 386 # name and its values respectively.
 387 #
 388 sub GetElementPropertiesData {
 389   my($ElementID) = @_;
 390   my($AtomicNumber);
 391 
 392   if ($AtomicNumber = _ValidateElementID($ElementID)) {
 393     return \%{$ElementDataMap{$AtomicNumber}};
 394   }
 395   else {
 396     return undef;
 397   }
 398 }
 399 
 400 #
 401 # Get names of all available element properties. A reference to  an array containing
 402 # names of all available properties is returned.
 403 #
 404 sub GetElementPropertiesNames {
 405   my($Mode);
 406   my($PropertyName, @PropertyNames);
 407 
 408   $Mode = 'ByGroup';
 409   if (@_ == 1) {
 410     ($Mode) = @_;
 411   }
 412 
 413   @PropertyNames = ();
 414   if ($Mode =~ /^Alphabetical$/i) {
 415     # AtomicNumber, ElementSymbol and ElementName are always listed first...
 416     push @PropertyNames, qw(AtomicNumber ElementSymbol ElementName);
 417     for $PropertyName (sort keys %ElementPropertyNamesMap) {
 418       if ($PropertyName !~ /^(AtomicNumber|ElementSymbol|ElementName)$/i) {
 419         push @PropertyNames, $PropertyName;
 420       }
 421     }
 422   }
 423   else {
 424     push @PropertyNames, @ElementPropertyNames;
 425   }
 426   return (wantarray ? @PropertyNames : \@PropertyNames);
 427 }
 428 
 429 #
 430 # Get names and units of all available element properties...
 431 # A reference to a hash array is returned with keys and values representing property
 432 # name and its units respectively. Names with no units contains empty strings as hash
 433 # values.
 434 #
 435 sub GetElementPropertiesNamesAndUnits {
 436 
 437    return \%ElementPropertyNamesMap;
 438 }
 439 
 440 #
 441 # Get units for a specific element property. An empty string is returned for a property
 442 # with no units.
 443 #
 444 sub GetElementPropertyUnits {
 445   my($PropertyName) = @_;
 446   my($PropertyUnits);
 447 
 448   $PropertyUnits = (exists($ElementPropertyNamesMap{$PropertyName})) ? $ElementPropertyNamesMap{$PropertyName} : undef;
 449 
 450   return $PropertyUnits;
 451 }
 452 
 453 #
 454 # Is it a known element? Input is either an element symol or a atomic number.
 455 #
 456 sub IsElement {
 457   my($ElementID) = @_;
 458   my($Status);
 459 
 460   $Status = (_ValidateElementID($ElementID)) ? 1 : 0;
 461 
 462   return $Status;
 463 }
 464 
 465 #
 466 # Is it a valid mass number for an element? Element ID is either an element symol or a atomic number.
 467 #
 468 sub IsElementNaturalIsotopeMassNumber {
 469   my($ElementID, $MassNumber) = @_;
 470   my($AtomicNumber, $Status);
 471 
 472   $Status = 0;
 473   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 474     return $Status;
 475   }
 476   if (exists $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}) {
 477     $Status = 1;
 478   }
 479 
 480   return $Status;
 481 }
 482 
 483 #
 484 # Is it an available element property?
 485 #
 486 sub IsElementProperty {
 487   my($PropertyName) = @_;
 488   my($Status);
 489 
 490   $Status = (exists($ElementPropertyNamesMap{$PropertyName})) ? 1 : 0;
 491 
 492   return $Status;
 493 }
 494 
 495 #
 496 # Implents GetElement<PropertyName> for a valid proprty name.
 497 #
 498 sub AUTOLOAD {
 499   my($ElementID) = @_;
 500   my($FunctionName, $PropertyName, $PropertyValue, $AtomicNumber);
 501 
 502   $PropertyValue = undef;
 503 
 504   use vars qw($AUTOLOAD);
 505   $FunctionName = $AUTOLOAD;
 506   $FunctionName =~ s/.*:://;
 507 
 508   # Only Get<PropertyName> functions are supported...
 509   if ($FunctionName !~ /^GetElement/) {
 510     croak "Error: Function, PeriodicTable::$FunctionName, is not supported by AUTOLOAD in PeriodicTable module: Only GetElement<PropertyName> functions are implemented...";
 511   }
 512 
 513   $PropertyName = $FunctionName;
 514   $PropertyName =~  s/^GetElement//;
 515   if (!exists $ElementPropertyNamesMap{$PropertyName}) {
 516     croak "Error: Function, PeriodicTable::$FunctionName, is not supported by AUTOLOAD in PeriodicTable module: Unknown element property name, $PropertyName, specified...";
 517   }
 518 
 519   if (!($AtomicNumber = _ValidateElementID($ElementID))) {
 520     return undef;
 521   }
 522   $PropertyValue = $ElementDataMap{$AtomicNumber}{$PropertyName};
 523   return $PropertyValue;
 524 }
 525 
 526 #
 527 # Get elements labels for group name specified using American or European style...
 528 #
 529 sub _GetElementsByGroupLabel {
 530   my($GroupLabel, $LabelStyle) = @_;
 531   my($GroupNumber);
 532 
 533   if ($LabelStyle =~ /^AmericanStyle$/i) {
 534     $GroupNumber = GetIUPACGroupNumberFromAmericanStyleGroupLabel($GroupLabel);
 535   }
 536   elsif ($LabelStyle =~ /^EuropeanStyle$/i) {
 537     $GroupNumber = GetIUPACGroupNumberFromEuropeanStyleGroupLabel($GroupLabel);
 538   }
 539 
 540   if (IsEmpty($GroupNumber)) {
 541     return (wantarray ? () : undef);
 542   }
 543 
 544   my($AtomicNumber, @GroupElements, @ElementSymbols);
 545   @ElementSymbols = ();
 546   if ($GroupNumber =~ /\,/) {
 547     my(@GroupNumbers);
 548 
 549     @GroupNumbers = split /\,/, $GroupNumber;
 550     for $GroupNumber (@GroupNumbers) {
 551       @GroupElements =  GetElementsByGroupNumber($GroupNumber);
 552       push @ElementSymbols, @GroupElements;
 553     }
 554   }
 555   else {
 556     @GroupElements =  GetElementsByGroupNumber($GroupNumber);
 557     push @ElementSymbols, @GroupElements;
 558   }
 559   return (wantarray ? @ElementSymbols : \@ElementSymbols);
 560 }
 561 
 562 #
 563 # Load PeriodicTableElementData.csv and PeriodicTableIsotopeData.csv files from
 564 # <MayaChemTools>/lib directory...
 565 #
 566 sub _LoadPeriodicTableElementData {
 567   my($ElementDataFile, $ElementIsotopeDataFile, $MayaChemToolsLibDir);
 568 
 569   $MayaChemToolsLibDir = GetMayaChemToolsLibDirName();
 570 
 571   $ElementDataFile =  "$MayaChemToolsLibDir" . "/data/PeriodicTableElementData.csv";
 572   $ElementIsotopeDataFile = "$MayaChemToolsLibDir" . "/data/PeriodicTableIsotopeData.csv";
 573 
 574   if (! -e "$ElementDataFile") {
 575     croak "Error: MayaChemTools package file, $ElementDataFile, is missing: Possible installation problems...";
 576   }
 577   if (! -e "$ElementIsotopeDataFile") {
 578     croak "Error: MayaChemTools package file, $ElementIsotopeDataFile, is missing: Possible installation problems...";
 579   }
 580 
 581   _LoadElementData($ElementDataFile);
 582   _LoadElementIsotopeData($ElementIsotopeDataFile);
 583 }
 584 
 585 #
 586 # Load PeriodicTableElementData.csv file from <MayaChemTools>/lib directory...
 587 #
 588 sub _LoadElementData {
 589   my($ElementDataFile) = @_;
 590 
 591   %ElementDataMap = ();
 592   @ElementPropertyNames = ();
 593   %ElementPropertyNamesMap = ();
 594   %ElementSymbolMap = ();
 595 
 596   # Load atomic properties data for all elements...
 597   #
 598   # File Format:
 599   #"AtomicNumber","ElementSymbol","ElementName","AtomicWeight","GroupNumber","GroupName","PeriodNumber","Block","GroundStateConfiguration","ValenceElectrons","GroundStateLevel","StandardState","CommonValences","LowestCommonValence","HighestCommonValence","CommonOxidationNumbers","LowestCommonOxidationNumber","HighestCommonOxidationNumber","BondLength(pm)","AtomicRadiusEmpirical(pm)","AtomicRadiusCalculated(pm)","CovalentRadiusEmpirical(pm)","VanderWaalsRadius(pm)","ElectronAffinity(kJ mol-1)","FirstIonizationEnergy(kJ mol-1)","PaulingElectronegativity(Pauling units)","SandersonElectronegativity(Pauling units)","AllredRochowElectronegativity(Pauling units)","MullikenJaffeElectronegativity(Pauling units)","AllenElectronegativity(Pauling units)","DensityOfSolid(kg m-3)","MolarVolume(cm3)","VelocityOfSound(m s-1)","YoungsModulus(GPa)","RigidityModulus(GPa)","BulkModulus(GPa)","PoissonsRatio(No units)","MineralHardness(No units)","BrinellHardness(MN m-2)","VickersHardness(MN m-2)","ElectricalResistivity(10-8 omega m)","Reflectivity(%)","RefractiveIndex(No units)","MeltingPoint(Celsius)","BoilingPoint(Celsius)","CriticalTemperature(Celsius)","SuperconductionTemperature(Celsius)","ThermalConductivity(W m-1 K-1)","CoefficientOfLinearExpansion(K-1 x 10^6)","EnthalpyOfFusion(kJ mol-1)","EnthalpyOfVaporization(kJ mol-1)","EnthalpyOfAtmization(kJ mol-1)","Color","Classification","DiscoveredBy","DiscoveredAt","DiscoveredWhen","OriginOfName"
 600   #
 601   #
 602   my($AtomicNumber, $ElementSymbol, $Line, $NumOfCols, $InDelim, $Index, $Name, $Value, $Units, @LineWords, @ColLabels);
 603 
 604   $InDelim = "\,";
 605   open ELEMENTDATAFILE, "$ElementDataFile" or croak "Couldn't open $ElementDataFile: $! ...";
 606 
 607   # Skip lines up to column labels...
 608   LINE: while ($Line = GetTextLine(\*ELEMENTDATAFILE)) {
 609     if ($Line !~ /^#/) {
 610       last LINE;
 611     }
 612   }
 613   @ColLabels= quotewords($InDelim, 0, $Line);
 614   $NumOfCols = @ColLabels;
 615 
 616   # Extract property names from column labels - and unit names where appropriate...
 617   @ElementPropertyNames = ();
 618   for $Index (0 .. $#ColLabels) {
 619     $Name = $ColLabels[$Index];
 620     $Units = "";
 621     if ($Name =~ /\(/) {
 622       ($Name, $Units) = split /\(/,  $Name;
 623       $Units =~ s/\)//g;
 624     }
 625     push @ElementPropertyNames, $Name;
 626 
 627     # Store element names and units...
 628     $ElementPropertyNamesMap{$Name} = $Units;
 629   }
 630 
 631   # Process element data...
 632   LINE: while ($Line = GetTextLine(\*ELEMENTDATAFILE)) {
 633     if ($Line =~ /^#/) {
 634       next LINE;
 635     }
 636     @LineWords = ();
 637     @LineWords = quotewords($InDelim, 0, $Line);
 638     if (@LineWords != $NumOfCols) {
 639       croak "Error: The number of data fields, @LineWords, in $ElementDataFile must be $NumOfCols.\nLine: $Line...";
 640     }
 641     $AtomicNumber = $LineWords[0]; $ElementSymbol = $LineWords[1];
 642     if (exists $ElementDataMap{$AtomicNumber}) {
 643       carp "Warning: Ignoring data for element $ElementSymbol: It has already been loaded.\nLine: $Line....";
 644       next LINE;
 645     }
 646 
 647     # Store all the values...
 648     %{$ElementDataMap{$AtomicNumber}} = ();
 649     for $Index (0 .. $#LineWords) {
 650       $Name = $ElementPropertyNames[$Index];
 651       $Value = $LineWords[$Index];
 652       $ElementDataMap{$AtomicNumber}{$Name} = $Value;
 653     }
 654   }
 655   close ELEMENTDATAFILE;
 656 
 657   # Setup the element symbol map as well...
 658   _SetupElementSymbolMap();
 659 }
 660 
 661 #
 662 # Load PeriodicTableIsotopeData.csv files from <MayaChemTools>/lib directory...
 663 #
 664 sub _LoadElementIsotopeData {
 665   my($ElementIsotopeDataFile) = @_;
 666 
 667   %ElementIsotopeDataMap = ();
 668   %ElementIsotopeDerivedDataMap = ();
 669 
 670   # Load isotope data for all elements...
 671   #
 672   # File format:
 673   # "Atomic Number","Isotope Symbol","Mass Number","Relative Atomic Mass","% Natural Abundnace"
 674   #
 675   # Empty values for "Relative Atomic Mass" and "% Natural Abundnace" imply absence of any
 676   # naturally occuring isotopes for the element.
 677   #
 678   my($InDelim, $Line, $NumOfCols, @ColLabels, @LineWords);
 679 
 680   $InDelim = "\,";
 681   open ISOTOPEDATAFILE, "$ElementIsotopeDataFile" or croak "Couldn't open $ElementIsotopeDataFile: $! ...";
 682 
 683   # Skip lines up to column labels...
 684   LINE: while ($Line = GetTextLine(\*ISOTOPEDATAFILE)) {
 685     if ($Line !~ /^#/) {
 686       last LINE;
 687     }
 688   }
 689   @ColLabels= quotewords($InDelim, 0, $Line);
 690   $NumOfCols = @ColLabels;
 691 
 692   my($AtomicNumber, $IsotopeSymbol, $MassNumber, $RelativeAtomicMass, $NaturalAbundance, %ZeroNaturalAbundanceMap);
 693   %ZeroNaturalAbundanceMap = ();
 694 
 695   # Process element data...
 696   LINE: while ($Line = GetTextLine(\*ISOTOPEDATAFILE)) {
 697     if ($Line =~ /^#/) {
 698       next LINE;
 699     }
 700     @LineWords = ();
 701     @LineWords = quotewords($InDelim, 0, $Line);
 702     if (@LineWords != $NumOfCols) {
 703       croak "Error: The number of data fields, @LineWords, in $ElementIsotopeDataFile must be $NumOfCols.\nLine: $Line...";
 704     }
 705     ($AtomicNumber, $IsotopeSymbol, $MassNumber, $RelativeAtomicMass, $NaturalAbundance) = @LineWords;
 706     if (exists $ZeroNaturalAbundanceMap{$AtomicNumber}) {
 707       # Only one isotope data line allowed for elements with no natural isotopes...
 708       carp "Warning: Ignoring isotope data for element with atomic number $AtomicNumber: Only one data line allowed for an element with no natural isotopes.\nLine: $Line...";
 709       next LINE;
 710     }
 711     if (IsEmpty($NaturalAbundance)) {
 712       $RelativeAtomicMass = 0;
 713       $NaturalAbundance = 0;
 714       $ZeroNaturalAbundanceMap{$AtomicNumber} = 1;
 715     }
 716     if (exists $ElementIsotopeDataMap{$AtomicNumber}) {
 717       # Additional data for an existing element...
 718       if (exists $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}) {
 719         carp "Warning: Ignoring isotope data for element with atomic number $AtomicNumber: It has already been loaded.\nLine: $Line...";
 720         next LINE;
 721       }
 722     }
 723     else {
 724       # Data for a new element...
 725       %{$ElementIsotopeDataMap{$AtomicNumber}} = ();
 726     }
 727     %{$ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}} = ();
 728     $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{IsotopeSymbol} = $IsotopeSymbol;
 729     $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{RelativeAtomicMass} = $RelativeAtomicMass;
 730     $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{NaturalAbundance} = $NaturalAbundance;
 731   }
 732   close ISOTOPEDATAFILE;
 733 
 734   _SetupElementIsotopeDerivedDataMap();
 735 }
 736 
 737 #
 738 # Map mass number of most abundant isotope for each element; additionally,
 739 # count number of isotopes as well.
 740 #
 741 sub _SetupElementIsotopeDerivedDataMap {
 742   my($AtomicNumber, $MassNumber, $NaturalAbundance, $MostNaturalAbundance, $MostAbundantMassNumber, $IsotopeCount);
 743 
 744   %ElementIsotopeDerivedDataMap = ();
 745 
 746   for $AtomicNumber (sort {$a <=> $b} keys %ElementIsotopeDataMap) {
 747     $IsotopeCount = 0;
 748     $MostAbundantMassNumber = 0;
 749     $MostNaturalAbundance = 0;
 750     MASSNUMBER: for $MassNumber (sort {$a <=> $b} keys %{$ElementIsotopeDataMap{$AtomicNumber}}) {
 751       $NaturalAbundance = $ElementIsotopeDataMap{$AtomicNumber}{$MassNumber}{NaturalAbundance};
 752       if (IsEmpty($NaturalAbundance)) {
 753         # No natural isotopes available...
 754         $MostAbundantMassNumber = $MassNumber;
 755         last MASSNUMBER;
 756       }
 757       if ($NaturalAbundance == 0) {
 758         # Not a natural isotope; Listed in periodic table data file to support non-natural
 759         # elements such as T. It's not included in natural isotope count...
 760         next MASSNUMBER;
 761       }
 762       $IsotopeCount++;
 763       if ($NaturalAbundance > $MostNaturalAbundance) {
 764         $MostAbundantMassNumber = $MassNumber;
 765         $MostNaturalAbundance = $NaturalAbundance;
 766       }
 767     }
 768     %{$ElementIsotopeDerivedDataMap{$AtomicNumber}} = ();
 769     $ElementIsotopeDerivedDataMap{$AtomicNumber}{IsotopeCount} = $IsotopeCount;
 770     $ElementIsotopeDerivedDataMap{$AtomicNumber}{MostAbundantMassNumber} = $MostAbundantMassNumber;
 771   }
 772 }
 773 
 774 #
 775 # Setup element symbol map...
 776 #
 777 sub _SetupElementSymbolMap {
 778   my($AtomicNumber, $ElementSymbol);
 779 
 780   %ElementSymbolMap = ();
 781 
 782   for $AtomicNumber (keys %ElementDataMap) {
 783     $ElementSymbol = $ElementDataMap{$AtomicNumber}{ElementSymbol};
 784     $ElementSymbolMap{$ElementSymbol} = $AtomicNumber;
 785   }
 786 }
 787 
 788 # Validate element ID...
 789 sub _ValidateElementID {
 790   my($ElementID) = @_;
 791   my($ElementSymbol, $AtomicNumber);
 792 
 793   if ($ElementID =~ /[^0-9]/) {
 794     # Assume atomic symbol...
 795     $ElementSymbol = $ElementID;
 796     if (exists $ElementSymbolMap{$ElementSymbol}) {
 797       $AtomicNumber = $ElementSymbolMap{$ElementSymbol};
 798     }
 799     else {
 800       return undef;
 801     }
 802   }
 803   else {
 804     # Assume atomic number...
 805     $AtomicNumber = $ElementID;
 806     if (!exists $ElementDataMap{$AtomicNumber}) {
 807       return undef;
 808     }
 809   }
 810   return $AtomicNumber;
 811 }
 812