Skip to content

Commit

Permalink
Redo lazy loading of formula, etc. Refs #72.
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Kienzle committed Oct 23, 2024
1 parent 3bee412 commit 520cac8
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 262 deletions.
333 changes: 71 additions & 262 deletions periodictable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"""

__docformat__ = 'restructuredtext en'
__all__ = ['elements', 'neutron_sld', 'xray_sld',
'formula', 'mix_by_weight', 'mix_by_volume'] # and all elements
__version__ = "2.0.0-pre"

import importlib

from . import core
from . import mass
from . import density
Expand All @@ -38,7 +38,76 @@
elements = core.PUBLIC_TABLE
mass.init(elements)
density.init(elements)
del mass, density

# Lazy loading of other element and isotope attributes
core.delayed_load(
['covalent_radius', 'covalent_radius_units', 'covalent_radius_uncertainty'],
lambda: covalent_radius.init(elements))
core.delayed_load(
['crystal_structure'],
lambda: crystal_structure.init(elements))
core.delayed_load(
['neutron'],
lambda: nsf.init(elements), isotope=True)
core.delayed_load(
['neutron_activation'],
lambda: activation.init(elements),
element=False, isotope=True)
core.delayed_load(
['xray'],
lambda: xsf.init(elements),
ion=True)
core.delayed_load(
['K_alpha', 'K_beta1', 'K_alpha_units', 'K_beta1_units'],
lambda: xsf.init_spectral_lines(elements))
core.delayed_load(
['magnetic_ff'],
lambda: magnetic_ff.init(elements))

# Lazy loading of modules and symbols from other modules. This is
# equivalent to using "from .formulas import formula" etc in __init__
# except that the import doesn't happen until the symbol is referenced.
# Using "from periodictable import formula" will import the symbol immediately.
# "from periodictable import *" will import all symbols, including the lazy
_LAZY_MODULES = [
'activation',
'covalent_radius',
'crystal_structure',
'nsf',
'xsf',
'magnetic_ff',
]
_LAZY_LOAD = {
'formula': 'formulas',
'mix_by_weight': 'formulas',
'mix_by_volume': 'formulas',
'neutron_sld': 'nsf',
'neutron_scattering': 'nsf',
'xray_sld': 'xsf',
}
def __getattr__(name: str):
module_name = _LAZY_LOAD.get(name, None)
if module_name is not None:
# Attr is in the lazy list: fetch name from the target module
module = importlib.import_module(f'{__name__}.{module_name}')
symbol = getattr(module, name)
globals()[name] = symbol
return symbol
if name in _LAZY_MODULES:
return importlib.import_module(f'{__name__}.{name}')
raise AttributeError(f"{__name__}.{name} not found")
def __dir__() -> list[str]:
return __all__

# Export variables for each element name and symbol.
__all__ = core.define_elements(elements, globals())
__all__ = [
'elements',
*__all__,
*sorted(_LAZY_MODULES),
*sorted(_LAZY_LOAD.keys()),
]

# Data needed for setup.py when bundling the package into an exe
def data_files():
Expand All @@ -62,263 +131,3 @@ def _finddata(ext, patterns):
_finddata('xsf', ['*.nff', 'read.me'])),
('periodictable-data', _finddata('.', ['activation.dat', 'f0_WaasKirf.dat']))]
return files

# Export variables for each element name and symbol.
__all__ += core.define_elements(elements, globals())

def _load_covalent_radius():
"""
covalent radius: average atomic radius when bonded to C, N or O.
"""
from . import covalent_radius
covalent_radius.init(elements)
core.delayed_load(['covalent_radius',
'covalent_radius_units',
'covalent_radius_uncertainty'],
_load_covalent_radius)

def _load_crystal_structure():
"""
Add crystal_structure property to the elements.
Reference:
*Ashcroft and Mermin.*
"""

from . import crystal_structure
crystal_structure.init(elements)
core.delayed_load(['crystal_structure'], _load_crystal_structure)

def _load_neutron():
"""
Neutron scattering factors, *nuclear_spin* and *abundance*
properties for elements and isotopes.
Reference:
*Rauch. H. and Waschkowski. W., ILL Nuetron Data Booklet.*
"""

from . import nsf
nsf.init(elements)
core.delayed_load(['neutron'], _load_neutron, isotope=True)

def _load_neutron_activation():
"""
Neutron activation calculations for isotopes and formulas.
Reference:
*IAEA 273: Handbook on Nuclear Activation Data.*
*NBSIR 85-3151: Compendium of Benchmark Neutron Field.*
"""
from . import activation
activation.init(elements)
core.delayed_load(['neutron_activation'], _load_neutron_activation,
element=False, isotope=True)

def _load_xray():
"""
X-ray scattering properties for the elements.
Reference:
*Center for X-Ray optics. Henke. L., Gullikson. E. M., and Davis. J. C.*
"""

from . import xsf
xsf.init(elements)
core.delayed_load(['xray'], _load_xray, ion=True)

def _load_emission_lines():
"""
X-ray emission lines for various elements, including Ag, Pd, Rh, Mo,
Zn, Cu, Ni, Co, Fe, Mn, Cr and Ti. *K_alpha* is the average of
K_alpha1 and K_alpha2 lines.
"""

from . import xsf
xsf.init_spectral_lines(elements)
core.delayed_load(['K_alpha', 'K_beta1', 'K_alpha_units', 'K_beta1_units'],
_load_emission_lines)

def _load_magnetic_ff():
"""
Magnetic Form Fators. These values are directly from CrysFML.
Reference:
*Brown. P. J.(Section 4.4.5)
International Tables for Crystallography Volume C, Wilson. A.J.C.(ed).*
"""

from . import magnetic_ff
magnetic_ff.init(elements)
core.delayed_load(['magnetic_ff'], _load_magnetic_ff)


# Constructors and functions
def formula(*args, **kw):
"""
Chemical formula representation.
Example initializers:
string:
m = formula( "CaCO3+6H2O" )
sequence of fragments:
m = formula( [(1, Ca), (2, C), (3, O), (6, [(2, H), (1, O)]] )
molecular math:
m = formula( "CaCO3" ) + 6*formula( "H2O" )
another formula (makes a copy):
m = formula( formula("CaCO3+6H2O") )
an atom:
m = formula( Ca )
nothing:
m = formula()
Additional information can be provided:
density (|g/cm^3|) material density
natural_density (|g/cm^3|) material density with natural abundance
name (string) common name for the molecule
table (PeriodicTable) periodic table with customized data
Operations:
m.atoms returns a dictionary of isotope: count for the
entire molecule
Formula strings consist of counts and atoms such as "CaCO3+6H2O".
Groups can be separated by '+' or space, so "CaCO3 6H2O" works as well.
Groups and be defined using parentheses, such as "CaCO3(H2O)6".
Parentheses can nest: "(CaCO3(H2O)6)1"
Isotopes are represented by index, e.g., "CaCO[18]3+6H2O".
Counts can be integer or decimal, e.g. "CaCO3+(3HO0.5)2".
For full details see help(periodictable.formulas.formula_grammar)
The chemical formula is designed for simple calculations such
as molar mass, not for representing bonds or atom positions.
However, we preserve the structure of the formula so that it can
be used as a basis for a rich text representation such as
matplotlib TeX markup.
"""
from . import formulas
return formulas.formula(*args, **kw)

def mix_by_weight(*args, **kw):
"""
Generate a mixture which apportions each formula by weight.
:Parameters:
*formula1* : Formula OR string
Material
*quantity1* : float
Relative quantity of that material
*formula2* : Formula OR string
Material
*quantity2* : float
Relative quantity of that material
...
*density* : float
Density of the mixture, if known
*natural_density* : float
Density of the mixture with natural abundances, if known.
*name* : string
Name of the mixture
:Returns:
*formula* : Formula
If density is not given, then it will be computed from the density
of the components, assuming equal volume.
"""
from . import formulas
return formulas.mix_by_weight(*args, **kw)

def mix_by_volume(*args, **kw):
"""
Generate a mixture which apportions each formula by volume.
:Parameters:
*formula1* : Formula OR string
Material
*quantity1* : float
Relative quantity of that material
*formula2* : Formula OR string
Material
*quantity2* : float
Relative quantity of that material
...
*density* : float
Density of the mixture, if known
*natural_density* : float
Density of the mixture with natural abundances, if known.
*name* : string
Name of the mixture
:Returns:
*formula* : Formula
Densities are required for each of the components. If the density of
the result is not given, it will be computed from the components
assuming the components take up no more nor less space because they
are in the mixture.
"""
from . import formulas
return formulas.mix_by_volume(*args, **kw)


def neutron_sld(*args, **kw):
"""
Compute neutron scattering length densities for molecules.
Returns scattering length density (real, imaginary and incoherent).
See :class:`periodictable.nsf.neutron_sld` for details.
"""
from . import nsf
return nsf.neutron_sld(*args, **kw)

def neutron_scattering(*args, **kw):
"""
Compute neutron scattering cross sections for molecules.
Returns scattering length density (real, imaginary and incoherent),
cross sections (coherent, absorption, incoherent) and penetration
depth.
See :func:`periodictable.nsf.neutron_scattering` for details.
"""
from . import nsf
return nsf.neutron_scattering(*args, **kw)

def xray_sld(*args, **kw):
"""
Compute neutron scattering length densities for molecules.
Either supply the wavelength (A) or the energy (keV) of the X-rays.
Returns scattering length density (real, imaginary).
See :class:`periodictable.xsf.Xray` for details.
"""
from . import xsf
return xsf.xray_sld(*args, **kw)


#del core, mass, density
36 changes: 36 additions & 0 deletions periodictable/formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,42 @@ def formula(compound=None, density=None, natural_density=None,
:Exceptions:
*ValueError* : invalid formula initializer
Example compounds:
string:
m = formula( "CaCO3+6H2O" )
sequence of fragments:
m = formula( [(1, Ca), (2, C), (3, O), (6, [(2, H), (1, O)]] )
molecular math:
m = formula( "CaCO3" ) + 6*formula( "H2O" )
another formula (makes a copy):
m = formula( formula("CaCO3+6H2O") )
an atom:
m = formula( Ca )
nothing:
m = formula()
Operations:
m.atoms returns {isotope: count, ...} for each atom in the compound.
Formula strings consist of counts and atoms such as "CaCO3+6H2O".
Groups can be separated by '+' or space, so "CaCO3 6H2O" works as well.
Groups and be defined using parentheses, such as "CaCO3(H2O)6".
Parentheses can nest: "(CaCO3(H2O)6)1"
Isotopes are represented by index, e.g., "CaCO[18]3+6H2O".
Counts can be integer or decimal, e.g. "CaCO3+(3HO0.5)2".
Density can be specified in the formula using, e.g., "H2O@1". Isotopic
formulas can use natural density, e.g., "D2O@1n", or the expected density
with that isotope, e.g., "[email protected]".
For full details see help(periodictable.formulas.formula_grammar)
The chemical formula is designed for simple calculations such
as molar mass, not for representing bonds or atom positions.
However, we preserve the structure of the formula so that it can
be used as a basis for a rich text representation such as
matplotlib TeX markup.
After creating a formula, a rough estimate of the density can be
computed using::
Expand Down
Loading

0 comments on commit 520cac8

Please sign in to comment.