Skip to content

mjziebarth/Cyantities

Repository files navigation

Cyantities

Cython-powered quantities.

Usage

Python

Cyantities ships two Python classes: Unit and Quantity. The Unit class represents a physical unit, that is, a reference vector in a basis of physical dimensions. In Cyantities, everything is based upon the SI (internally all units are represented as an array of integers, each of which represents the powers of an SI basic unit).

The Unit class can be initialized by passing a string representation of the unit:

from cyantities import Unit

unit0 = Unit('km')
unit1 = Unit('m/(s^2)')
unit2 = Unit('kg m s^-2')

The Quantity class represents numbers that are associated with a unit: physical quantities.

from cyantities import Quantity

For convenience and efficiency, the numbers can be either a single float (essentially leading to a (float,Unit) tuple) or a NumPy array. See, for instance, the following code excerpt from the example of a ball throw with air friction (examples/parabola/run.py)

t = Quantity(np.linspace(0.0, 6.0), 's')
x0 = Quantity(0.0, 'm')
y0 = Quantity(2.1, 'm')
v = Quantity(145.0, 'km h^-1')

Here, the first line creates an equidistantly spaced set of time points between 0 and 6 seconds. The second and third line set the initial position of the ball, two scalars with unit metre, to above head height of an average human. The last line sets the initial velocity to 145 kilometers per hour.

To convert quantities back to pure numbers, unit dimensions need to be canceled out through multiplication or division. See, for instance, the following lines of examples/parabola/run.py that plot the trajectory of the ball thrown with firction:

import matplotlib as plt

# ... more code here, resulting in the trajetories 'x' and 'y' ...

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(np.array(x / Unit('m')), np.array(y / Unit('m')), marker='.')

The last line highlights an important feature of the Quantity class: if, and only if, a Quantity instance is dimensionless, it can be converted to a NumPy array. This conversion can be automatic via the NumPy __array__ interface. This special method is added dynamically to dimensionless Quantity instances, allowing automatic conversions from the NumPy side like

import numpy as np
z = np.exp(Quantity(np.arange(3), 'm') / Unit('cm'))

but preventing numeric operations on quantities with a physical dimension:

z = np.exp(Quantity(np.arange(3), 'm')) # raises an exception

Besides multiplication and division with other quantities and units, Quantity instances can be added to and subtracted from quantities of the same unit dimension, taking into account potential scale differences in the physical units.

Unit String Representation

Two methods (rules) are available to specify units. Both methods accept a string representation of the unit and parse that string assuming a certain formatting. A description of the two rules follows.

Coherent SI Rule

The coherent SI-style string representation has to be of the form 'u0 u1 u3^2 u4^-1 u5^-3'. Here, units are demarked by spaces (multiplication signs * can also be used). Integer unit powers, including negative, follow the unit representation and are indicated by the caret ^.

Note: Any order of the input units is acceptable.

Nominator-Denominator Rule

The nominator-denominator rule string representation has to be of the form 'u0*u1*u3^2/(u4*u5^3)', where u0 is the first unit including prefix (e.g. km), and so forth. Units are demarked by multiplication signs *, integer unit powers follow the unit representation and are indicated by the caret ^. All negative powers of units have to follow a single slash /, be enclosed in parantheses, and be positive therein.

C++ and Boost.Units

The main reason for developing Cyantities was to have a translation utility of unit-associated quantities from the Python world to the Boost.Units library. The canonical means to do so with Cyantities is through an intermediary Cython step (Python → Cython → C++).

Users will create units and quantities using the Unit and Quantities units of the Cyantities package. Importing the Cyantities Cython API, the cyantities::Unit C++ class, which is backing both Python classes, is exposed. This C++ class can then be transformed into a Boost.Units quantity, performing runtime checks of the dimensional correctness of the data passed from the Python level. Once this is done, the numerical data can similarly be transformed from the Python objects to the Boost.Units-powered C++ library.

The interaction of Cyantities with Boost.Units is best explained through an example. See the example of a ball throw with gravity and friction in examples/parabola for a blueprint of how to use Cyantities with Boost.Units, and the example of gravitational force on different masses in examples/gravity for different methods to iterate vector-valued quantities in C++.

Python Known Units

The following basic units are currently implemented in Cyantities and can be used to compose units based on the coherent SI or the nominator-denominator rule:

Python string Unit Comment
"1" dimensionless no prefix allowed
"m" metre
"kg" kilogram
"s" second
"A" Ampère
"K" Kelvin
"mol" mole
"cd" candela
"rad" radian Follow Boost.Units
"sr" steradian Follow Boost.Units

The following SI-derived units are similarly available:

Python string Unit Comment
"Pa" Pascal
"J" Joule
"Hz" Hertz
"N" Newton
"W" Watt
"C" Coulomb
"V" Volt
"F" Farad
"Ω" Ohm
"S" Siemens
"Wb" Weber
"T" Tesla
"H" Henry
"lm" lumen
"lx" lux
"Bq" Becquerel
"Gy" Gray
"Sv" Sievert
"kat" katal

Other units include:

Python string Unit Comment
"erg" erg (CGS units)
"g" gram
"h" hour

The temperature scales °C and °F are not supported as Python strings since they are not proportional to Kelvin and require an offset. Please define all your temperatures in K.

License

This software is licensed under the European Public License (EUPL) version 1.2 or later.

Changelog

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.5.0] - 2024-09-22

Added

  • Support for dtype and copy parameters in Quantity._array.
  • Added typing stubs for Unit and Quantity.

Changed

  • Remove use of deprecated numpy.array with copy=False.
  • Removed internal inconsistency in how scalar and array-valued Quantities were handled in the Quantity.wrapper() routine. Now, scalar-valued quantities can similarly be filled from the C++ side.
  • Prevent NumPy from creating an object array on left-hand multiplication by setting __array_ufunc__ = None.

[0.4.0] - 2024-08-12

Added

  • Indexing of matrix-valued Quantity instances.
  • Absolute for Quantity instance.

[0.3.0] - 2024-08-11

Added

  • Add computation of unit powers in C++ and Python.
  • Add unary negation operator to Quantity.

Changed

  • Fix array values of Quantity with dimension larger than one causing runtime errors.
  • Use _val_object instead of _val_array to obtain NDArray string representation.
  • Fix conv factor not honored when calling Unit(dec_exp, conv) constructor.
  • Remove the internal _val_array field entirely due to its (apparent?) inability to handle variable dimension.

[0.2.1] - 2024-08-04

Changed

  • Fix check in Quantity not considering integers as valid scalars.

[0.2.0] - 2024-08-04

Added

  • Add shape method for Quantity, which allows to query the (array-) shape of the underlying data.

[0.1.0] - 2024-05-05

Added

  • Add zeros_like generator function for Quantity (Cython only)
  • Add the iter() and const_iter() templated methods to C++ QuantityWrapper class, allowing for the use of range-based for loops and range adaptor closures (|-operator syntax) in compile-time provided units.
  • Add the gravity example that showcases different methods to iterate vector-valued quantities in C++.
  • Add benchmark for different iteration methods.

[0.0.3] - 2024-04-24

Changed

  • Fixed the installation requirements and source distribution manifest.
  • Add version coherence test script.

[0.0.2] - 2024-04-23

Changed

  • First release