MayaChemTools

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