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