\(\renewcommand\AA{\text{Å}}\)
Adding properties¶
The periodic table is extensible. Third party packages can add attributes to the table, and they will appear in all of the elements.
In order to add a new property to the table, you need to define
a python package which contains the required information, and can
attach the information to the periodic table so that it is
available on demand. This is done with the function init(table)
in
your table extension.
This example adds the attribute discoverer
to each element. First
create the file discoverer/core.py
:
"""
Partial table of element discoverers.
From http://en.wikipedia.org/wiki/Discoveries_of_the_chemical_elements.
"""
import periodictable.core
def init(table, reload=False):
if 'discoverer' in table.properties and not reload: return
table.properties.append('discoverer')
# Set the default, if any
periodictable.core.Element.discoverer = "Unknown"
# Not numeric, so no discoverer_units
# Load the data
for name,person in data.items():
el = table.name(name)
el.discoverer = person
data = dict(
arsenic="Jabir ibn Hayyan",
antimony="Jabir ibn Hayyan",
bismuth="Jabir ibn Hayyan",
phosphorus="H. Brand",
cobalt="G. Brandt",
platinum="A. de Ulloa",
nickel="A.F. Cronstedt",
magnesium="J. Black",
)
Now that we have defined the init(table)
function, we need a way to call it.
The simplest solution is to load it directly when your package is imported.
In the current example, this could be done by adding the following
line to the end of the file:
init(periodictable.core.elements)
This would be fine for the current example because the table size is
small and load time is fast. For large tables, you may wish to
delay loading the table data until it is needed. To do this, we
use the delayed_load
function in our
package init file discoverer/__init__.py
:
import periodictable.core
# Delayed loading of the element discoverer information
def _load_discoverer():
"""
The name of the person or group who discovered the element.
"""
from . import core
core.init(periodictable.core.default_table())
periodictable.core.delayed_load(['discoverer'], _load_discoverer)
The first argument to delayed_load is the list of all attributes that will be defined when the module is loaded. The second argument is the loading function, whose docstring will appear as the attribute description for each attribute in the first list.
Check that it works:
>>> import discoverer
>>> import periodictable
>>> print(periodictable.magnesium.discoverer)
J. Black
Isotope and ion specific data is also supported. In this case we
need a data table that contains information for each isotope of
each element. The following example uses a dictionary of elements,
with a dictionary of isotopes for each. It adds the shells
attribute to Fe[56] and Fe[58].
Define shelltable/core.py
:
"""
Example of isotope specific extensions to the periodic table.
"""
from periodictable.core import Isotope
def init(table, reload=False):
if 'shells' in table.properties and not reload: return
table.properties.append('shells')
# Set the default. This is required, even if it is only
# setting it to None. If the attribute is missing then
# the isotope data reverts to the element to supply the
# value, which is almost certainly not what you want.
Isotope.shells = None
# Load the data
for symbol,eldata in data.items():
el = table.symbol(symbol)
for iso,isodata in eldata.items():
el[iso].shells = isodata
# Define the data
data = dict(
Fe = {56: "56-Fe shell info",
58: "58-Fe shell info",
},
)
Again, we are going to initialize the table with delayed loading.
In this case it is very important that we set the isotope=True
keyword in the delayed_load
call. If we don’t, then the magic we
use to return the correct value after loading the new table information
fails. Since unknown attributes are delegated to the underlying
element, the value for the natural abundance will be returned
instead. On subsequent calls the isotope specific value will be
returned.
This is demonstrated in shelltable/__init__.py
:
import periodictable.core
# Delayed loading of the element discoverer information
def _load():
"""
The name of the person or group who discovered the element.
"""
from . import core
core.init(periodictable.core.default_table())
periodictable.core.delayed_load(['shells'], _load,
isotope=True, element=False)
Check that it works:
>>> import shelltable
>>> import periodictable
>>> print(periodictable.Fe[56].shells)
56-Fe shell info
>>> print(periodictable.Ni[58].shells)
None
Ion specific data is more complicated, particularly because of the
interaction with isotopes. For example, Ni[58].ion[3]
should have
the same neutron scattering factors as Ni[58]
(the neutron is
only sensitive to the nucleus), but different scattering factors
from Ni.ion[3]
. X-rays are sensitive to the electronic structure
of the atom and not the nucleus, so Ni[58].ion[3].xray.f0(Q)
and Ni.ion[3].xray.f0(Q)
are the same but different
from Ni.xray.f0(Q)
.
Current support for ion dependent properties is awkward. The X-ray
table periodictable.xsf
creates a specialized structure
for each ion as it is requested. The magnetic form factor table
periodictable.magnetic_ff
does not try to support ion.magnetic_ff
directly, but instead the user must request ion.magnetic_ff[ion.charge]
.
Support for ion mass, which is isotope mass adjusted for the number of
electrons is built into the Ion class. There are not yet any examples of
extension tables that depend on both isotope and ion.
Be sure to use the ion=True
keyword for delayed_load
when the
table extension contains ion specific information.