Unit handling for users

Unit handling for users#

Eradiate tries hard to handle physical quantities correctly and uses the Pint unit handling library to do so. A general reason for that is that poor unit handling can lead to actual fiascos. Another reason, more specific to Eradiate, is that this enables for dynamic quantity conversion, making it possible:

  • for users: to specify the units in which they want to provide input;

  • for developers: to change units with which they work (e.g. to scale scene dimensions dynamically).

Important

Under the hood, most unit handling facilities are implemented as part of the standalone Pinttrs library.

This guide presents how unit handling is documented in the API reference and how to handle units in Eradiate as a user. For a technical view on unit handling, see the Pinttrs documentation and source code.

Note

It is strongly advised to—at least—get familiar with Pint to fully take advantage of Eradiate’s unit support.

Units in Eradiate#

Eradiate usually works with the International System of units. However, it also understands that users might use a different unit system. Finally, Eradiate knows that it can be better to specify kernel scenes using yet another unit system.

When a quantity is implicitly known as dimensional, it can be superfluous to require a user to specify units—it’s so obvious!

Example

A class creates a tree object in a scene. The tree is parametrised by its height, and it’s obvious that it should be specified in metres. Therefore, a user would most certainly like that a call like

Tree(height=3.0)

creates a tree with a height of 3 metres.

However, it can happen that the “obvious” unit may vary depending on context. For instance, an atmosphere can be kilometre-sized; however, it is common practice to use metres to express altitude values. What is the “obvious” unit, then? Some user could say it is the kilometre, another might prefer using the metre. For this reason, Eradiate keeps track of default units used to configure its objects using a Pinttrs UnitContext instance. This configuration unit context (eradiate.unit_context_config, abbreviated as ucc) is used to attach units to dimensional quantities when the user does not specify them. Default configuration units are the SI units; but a user can override them if it seems more convenient in their context.

Example

>>> import eradiate
>>> from eradiate import unit_context_config as ucc
>>> from eradiate.scenes.atmosphere import HomogeneousAtmosphere
>>> eradiate.set_mode("mono")
>>> with ucc.override(length="km"):  # Temporarily set default length unit to kilometre
...     my_atmosphere = HomogeneousAtmosphere(top=100.0)
>>> my_atmosphere.top
<Quantity(100.0, 'kilometer')>

This will create a HomogeneousAtmosphere object with a height of 100 km. It is equivalent to the default

>>> my_atmosphere = HomogeneousAtmosphere(top=100e3)
>>> my_atmosphere.top
<Quantity(100000.0, 'meter')>

Fields specified as “unit-enabled” are stored as Pint Quantity objects and can be passed as such. Eradiate uses Pint’s application unit registry and provides an internal variable pointing to it (eradiate.unit_registry, abbreviated as ureg), which must be used to define units.

Warning

Eradiate uses Pint’s current application registry at import time. This means that:

  • if one is required, a custom application registry must be declared before issuing import eradiate;

  • subsequent modifications of the application registry using pint.set_application_registry() will not propagate to Eradiate and will break Eradiate’s unit support.

Another way of initialising our 100 km-high atmosphere would then be

>>> from eradiate import unit_registry as ureg
>>> my_atmosphere = HomogeneousAtmosphere(top=100.0 * ureg.km)
>>> my_atmosphere.top
<Quantity(100.0, 'kilometer')>

If one tries to set top with a value which has wrong units, a UnitsError will be raised:

>>> HomogeneousAtmosphere(top=100.0 * ureg.s)
Traceback (most recent call last):
    ...
pint.errors.DimensionalityError: Cannot convert from 'kilometer' ([length]) to 'second' ([time])

Field unit documentation#

Eradiate documents fields with units by mentioning them as unit-enabled. For those fields, automatic conversion of unitless values is implemented. Default units can be fixed (i.e. invariant): in that case, units will be specified directly in the documentation. Default units can also be dynamically selected at runtime by the user through Eradiate’s configuration unit context: in that case, default units are documented with a string with the following structure: <unit_context>[<quantity>] where

  • <unit_context> is either ucc for configuration unit context or uck for kernel unit context;

  • <quantity> is the physical quantity ID used to query the default unit set (see PhysicalQuantity for a list of available quantity IDs).

Units fetching their defaults at runtime from unit contexts can be overridden using the pinttrs.UnitContext.override() method.