Units and quantities¶
The tripper.units subpackage provides support for using Pint to work with units and quantites defined in ontologies.
Currently, only EMMO and EMMO-based ontologies can be used as a source for units and quantities. However, since EMMO includes references to the QUDT and OM ontologies, it is also possible to work with IRIs for these ontologies.
Note
Currently the support for OM is weak. Improvements are planned.
Unit registry¶
The UnitRegistry in tripper.units is a subclass of the Pint unit registry. By default it is populated with units from EMMO.
>>> from tripper.units import UnitRegistry
>>> ureg = UnitRegistry()
The registry provides attribute and item access to units based on their EMMO prefLabel or symbol.
>>> ureg.Pascal
<Unit('Pascal')>
>>> ureg.Pa
<Unit('Pascal')>
By convention, EMMO units are written in "CamelCase". However, unit access also works with "snake_case":
>>> ureg.pascal
<Unit('Pascal')>
>>> ureg.newton_square_metre
<Unit('NewtonSquareMetre')>
Item access creates a subclass of a Pint quantity representation (see Working with quantities):
>>> ureg["Pa"]
<Quantity(1, 'Pascal')>
>>> ureg["A/m²"]
<Quantity(1.0, 'Ampere / Metre ** 2')>
Extra unit registry methods¶
Tripper adds some extra methods to the unit registry on top of what is already provided by Pint, including:
- get_unit(): Returns a Pint unit object derived from unit name, symbol, IRI (supporting EMMO, QUDT and OM), or unit code defined in the ontology.
- get_unit_info(): Returns a dict with attribute access providing additional information about the unit.
- get_quantity(): Returns the Pint quantity (i.e. value and unit) of a quantity in the ontology.
- load_quantity(): Loads a quantity from a triplestore.
- save_quantity(): Saves a quantity to a triplestore.
- set_as_default(): Set the current unit registry as the default. This allows to access the registry with the get_ureg() method.
- clear_cache(): Clear caches.
Here we will only discuss get_unit() and get_unit_info() methods. See Accessing quantities in a triplestore and Setting up custom unit registry for the rest.
For example:
>>> ureg.get_unit(name="Metre") # name
<Unit('Metre')>
>>> ureg.get_unit(symbol="H/Ω") # EMMO symbol
<Unit('HenryPerOhm')>
>>> ureg.get_unit(symbol="H.Ohm-1") # UCUM symbol
<Unit('HenryPerOhm')>
>>> ureg.get_unit(iri="https://w3id.org/emmo#Metre") # EMMO IRI
<Unit('Metre')>
>>> ureg.get_unit(iri="http://qudt.org/vocab/unit/HR") # QUDT IRI
<Unit('Hour')>
>>> ureg.get_unit(iri="http://www.ontology-of-units-of-measure.org/resource/om-2/cubicMetre") # OM IRI
<Unit('CubicMetre')>
>>> ureg.get_unit(unitCode="HUR")
<Unit('Hour')>
When you have a unit, you can also ask it for its IRI using its emmoIRI
, qudtIRI
and omIRI
properties:
>>> ureg.CubicMetre.emmoIRI
'https://w3id.org/emmo#CubicMetre'
>>> ureg.CubicMetre.qudtIRI
'http://qudt.org/vocab/unit/M3'
>>> ureg.CubicMetre.omIRI
'http://www.ontology-of-units-of-measure.org/resource/om-2/cubicMetre'
Units have an info property providing a dict with attribute access with description of the unit provided by the ontology. It contains the following fields:
- name: Preferred label.
- description: Unit description.
- aliases: List of alternative labels.
- symbols: List with unit symbols.
- dimension: Named tuple with quantity dimension.
- emmoIRI: IRI of the unit in the EMMO ontology.
- qudtIRI: IRI of the unit in the QUDT ontology.
- omIRI: IRI of the unit in the OM ontology.
- ucumCodes: List of UCUM codes for the unit.
- unitCode: UNECE common code for the unit.
- multiplier: Unit multiplier.
- offset: Unit offset.
This dict can also be accessed with the get_unit() method.
For example:
>>> info = ureg.CubicMetre.info
>>> info.name
'CubicMetre'
>>> info.dimension
Dimension(T=0, L=3, M=0, I=0, H=0, N=0, J=0)
The same dict can also be accessed from the unit registry with the get_unit_info()
method.
>>> info = ureg.get_unit_info("CubicMetre")
>>> info.name
'CubicMetre'
Working with quantities¶
Physical quantities consisting of a numerical value and a unit, can be constructed in several ways.
For example by using ureg.Quantity() with two arguments
>>> length = ureg.Quantity(6, "km")
>>> length
<Quantity(6, 'KiloMetre')>
or with a single string argument
>>> time = ureg.Quantity("1.2 h")
>>> time
<Quantity(1.2, 'Hour')>
or by multiplying a numerical value with a unit
>>> pressure = 101325 * ureg.Pa
>>> pressure
<Quantity(101325, 'Pascal')>
The magnitude and unit of a quantity can be accessed individually with the properties m
and u
:
>>> pressure.m
101325
>>> pressure.u
<Unit('Pascal')>
By default, EMMO does not include prefixed units (with a few exceptions). It is therefore not uncommon to have a quantity with a unit not in the ontology. For example:
>>> duration = 1.2 * ureg.kh
>>> duration
<Quantity(1.2, 'KiloHour')>
# This raises an exception since KiloHour is not in the ontology
>>> duration.u.info
Traceback (most recent call last):
...
tripper.units.units.MissingUnitError: name=KiloHour
You can use to to_ontology_units() method (or its in-place variant ito_ontology_units()) to rescale the quantity to a unit that exists in the ontology:
>>> duration.ito_ontology_units()
>>> f"{duration:.1f}" # avoid rounding errors
'50.0 Day'
# The unit now has a `info` property
>>> duration.u.info.qudtIRI
'http://qudt.org/vocab/unit/DAY'
The get_quantity() method allows to represent a physical quantity in the ontology as a Pint quantity. By default access is by name (prefLabel):
>>> ureg.get_quantity("Energy")
<Quantity(1.0, 'Joule')>
# A value can also be provided (in units of the base SI units)
>>> ureg.get_quantity("Energy", value=2.5)
<Quantity(2.5, 'Joule')>
>>> q = ureg.get_quantity("Energy", value=1e-19)
>>> f"{q:.4~P}"
'0.6242 eV'
Access by "emmoIRI", "qudtIRI", "omIRI", "iupacIRI" or "iso80000Ref" is also possible:
>>> from tripper import EMMO
>>> ureg.get_quantity(iri=EMMO.Acceleration)
<Quantity(1.0, 'MetrePerSquareSecond')>
>>> ureg.get_quantity(iso80000Ref="3-9.1") # also acceleration
<Quantity(1.0, 'MetrePerSquareSecond')>
Quantities as literals¶
Quantities are also understood by the Literal constructor
>>> from tripper import Literal
>>> literal = Literal(pressure)
>>> literal
Literal('101325 Pa', datatype='https://w3id.org/emmo#EMMO_799c067b_083f_4365_9452_1f1433b03676')
# Check the label of the datatype
>>> from tripper import EMMO
>>> EMMO._get_labels(literal.datatype)
['SIQuantityDatatype']
which uses the emmo:SIQuantityDatatype
datatype.
The Literal.value property and Literal.n3() method can be used to convert back to a quantity or represent it in N3 notation:
>>> literal = Literal(pressure)
>>> literal.value
<Quantity(101325, 'Pascal')>
>>> literal.n3()
'"101325 Pa"^^<https://w3id.org/emmo#EMMO_799c067b_083f_4365_9452_1f1433b03676>'
Accessing quantities in a triplestore¶
Lets do a small calculation using the quantities constructed above:
>>> mean_speed = length / time
>>> mean_speed
<Quantity(5.0, 'KiloMetre / Hour')>
and store our calculated mean_speed
to a triplestore:
>>> from tripper import EMMO, Triplestore
>>> ts = Triplestore(backend="rdflib")
>>> NS = ts.bind("", "http://example.com#")
>>> ureg.save_quantity(ts, mean_speed, iri=NS.MeanSpeed, type=EMMO.Speed)
Above we have created a new triplestore, bound empty prefix to the namespace
http://example.com#
and saved the calculated mean_speed
to a new individual
with IRI http://example.com#MeanSpeed
.
The final type
argument to ureg.save_quantity() states that our new individual
will be an individual of the class emmo:Speed
.
The content of the triplestore is now
>>> print(ts.serialize())
@prefix : <http://example.com#> .
@prefix emmo: <https://w3id.org/emmo#> .
<BLANKLINE>
:MeanSpeed a emmo:EMMO_81369540_1b0e_471b_9bae_6801af22800e ;
emmo:EMMO_42806efc_581b_4ff8_81b0_b4d62153458b "5.0 km/h"^^emmo:EMMO_799c067b_083f_4365_9452_1f1433b03676 .
<BLANKLINE>
<BLANKLINE>
By default ureg.save_quantity() saves the quantity as an individual using the emmo:SIQuantityDatatype
datatype.
But, the ureg.save_quantity() method has also options for saving the quantity as a class (argument tbox=True
) or to represent the quantity using the emmo:hasNumericalPart
and emmo:hasReferencePart
properties (argument si_datatype=False
).
Loading a quantity from the triplestore can be with ureg.load_quantity():
>>> q = ureg.load_quantity(ts, iri=NS.MeanSpeed)
>>> q
<Quantity(5.0, 'KiloMetre / Hour')>
Setting up custom unit registry¶
You can extend the default set of units by creating a domain or application ontology with additional custom units. It should import EMMO to get the default units.
Use the url
and name
options when instantiating the unit registry
>>> ureg = UnitRegistry(url="http://custom.org/myunits.ttl", name="myunits-0.3.2") # doctest: +SKIP
where url
is the URL or file path to the ontology and name
is a, preferred versioned, name for the custom ontology used for caching.
Typically you create the unit registry when initialising your application. After creating it, you can call the set_as_default() method.
>>> ureg.set_as_default() # doctest: +ELLIPSIS
<tripper.units.units.UnitRegistry object at 0x...>
This will allow to retrieve the custom unit register from anywhere in your application using the get_ureg() function
>>> from tripper.units import get_ureg
>>> ureg = get_ureg()
Tips & tricks¶
Tripper caches the ontology and Pint units definition file for performance reasons. If the ontology has been updated, you may need to clear the cache. That can either be done manually or by calling the ureg.clear_cache() method.
For manual deletion of the cache files, the cache directory can be found using the ureg._tripper_cachedir
attribute.