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