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