Customizing elements
Element generation can be customized a number of ways by creating a new class with Elementable()
.
You can change:
whether attributes have units attached
the base class used for each Element (default: namedtuple)
the number of decimal places to round each floating point value to
the JSON file from which the elements are created
Units
It is relatively easy to use a number of different units packages.
These include OpenFF Units, OpenMM Units, Pint Units, and Unyt.
All that’s needed is to define units
as a dictionary, where
the expected unit of each attribute is given. These units
get multiplied with the bare integers or floating point numbers in
the JSON file used.
In [1]: import elementable as elm
In [2]: from openff.units import unit as offunit
In [3]: OpenFFElements = elm.Elementable(
...: units=dict(
...: mass=offunit.amu,
...: covalent_radius=offunit.angstrom
...: )
...: )
...:
Each element has now units defined on the object.
In [4]: offh = OpenFFElements(atomic_number=1)
In [5]: offh
Out[5]: Element(name='hydrogen', symbol='H', atomic_number=1, mass=<Quantity(1.00782503, 'unified_atomic_mass_unit')>, period=1, group=1, covalent_radius=<Quantity(0.31, 'angstrom')>)
However, units are not included in searching as keys to the Element registry. That’s because many of the packages are not hashable.
In [6]: OpenFFElements.registry.mass[1.0078]
Out[6]: Element(name='hydrogen', symbol='H', atomic_number=1, mass=<Quantity(1.00782503, 'unified_atomic_mass_unit')>, period=1, group=1, covalent_radius=<Quantity(0.31, 'angstrom')>)
Nonetheless, you can search for elements with unit-associated attributes.
In [7]: OpenFFElements(mass=1.0078 * offunit.amu)
Out[7]: Element(name='hydrogen', symbol='H', atomic_number=1, mass=<Quantity(1.00782503, 'unified_atomic_mass_unit')>, period=1, group=1, covalent_radius=<Quantity(0.31, 'angstrom')>)
You can even search with different, but compatible, units.
In [8]: OpenFFElements(mass=1.673532838315319e-24 * offunit.g)
Out[8]: Element(name='hydrogen', symbol='H', atomic_number=1, mass=<Quantity(1.00782503, 'unified_atomic_mass_unit')>, period=1, group=1, covalent_radius=<Quantity(0.31, 'angstrom')>)
Base class
NamedTuples were chosen as the Element base class as they are natively JSON-serializable.
In [9]: import json
In [10]: h = elm.Elements.registry.atomic_number[1]
In [11]: json.dumps(h)
Out[11]: '["hydrogen", "H", 1, 1.00782503223, 1, 1, 0.31]'
However, this representation may not be fantastic. You can create a custom representation with another class, such as a Pydantic BaseModel.
In [12]: from pydantic import BaseModel
In [13]: PydanticElements = elm.Elementable(
....: element_cls=BaseModel
....: )
....:
In [14]: h = PydanticElements(atomic_number=1)
In [15]: h.json()
Out[15]: '{"name": "hydrogen", "symbol": "H", "atomic_number": 1, "mass": 1.00782503223, "period": 1, "group": 1, "covalent_radius": 0.31}'
Decimal place precision
The number of places to round floating point attributes is a user-chosen value. You can make it more or less precise.
In [16]: LessPreciseElement = elm.Elementable(
....: decimals=0
....: )
....:
In [17]: LessPreciseElement(mass=1)
Out[17]: (Element(name='hydrogen', symbol='H', atomic_number=1, mass=1.00782503223, period=1, group=1, covalent_radius=0.31),)
JSON source
By default, Elementable creates elements from a file packaged in the library.
This may not contain the best values for you.
You can pass in json_file
to create Elements from a different source.
These can have arbitrary attributes.
The data source may not have the symbol
attribute normally used
to categorize entries in the returned elements container.
The key_attr
keyword can be used to choose which attribute to use as a key.
Note that the key_attr
attribute must be a string value, and unique for each element.
For a silly example:
In [18]: from elementable.tests.datafiles import VEGETABLES_JSON
In [19]: Vegetables = elm.Elementable(json_file=VEGETABLES_JSON, key_attr="name")
In [20]: print(Vegetables.carrot)
Element(name='carrot', color='orange', n_leaves=3, weight=100)