MayaChemTools

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