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)