Units in Arbor

Note

This is a work in progress. The near goal term is to make this coverage complete, but expect some exceptions. Notably, the interfaces of individual mechanism are not yet integrated, since NMODL files – despite explicitly specifying units – do not make good use of the feature.

A large part of the interface of Arbor – both in C++ and Python – is covered by units of measurement. This gives the API a way to communicate the intended units of parameters and return values and users can largely use their preferred system of units, as automatic conversion is provided. For performance reasons, this extends to the outermost layer only, after which Arbor uses its own internal system of measurement.

We leverage the units library published by LLNL.

Arbor is focussed on SI units, and we provide the following presets for both the C++ and Python modules.

Provided dimensionalities and units

Dimension

Unit

Temperature

Kelvin Celsius

Length

m cm mm um nm

Time

s ms us ns

Resistance

Ohm kOhm MOhm

Conductivity

S mS uS

Current

A mA uA nA pA

Potential

V mV

Frequency

Hz kHz

Capacity

F mF uF nF pF

Area

m2 cm2 mm2 um2 nm2

Charge

C

Mol

mol

Molarity

M = mol/l mM

Angle

rad deg

Further units may be derived from existing ones by mean of multiplication and division with the obvious semantics, the existing metric prefixes, or by extending the catalogue of units via the underlying units library.

Provided metric prefixes

Prefix

Scale

Prefix

Scale

pico

1e-12

kilo

1e3

nano

1e-9

mega

1e6

micro

1e-6

giga

1e9

milli

1e-3

centi

1e-2

Parameters are passed into Arbor via a quantity, which comprise a value and a unit. We construct a quantity by multiplication of a scalar value by a unit. Multiplication of two quantities will result in the pointwise product of the values and units; like one would expect.

# two kilometers, dimension is length
l = 2 * km

# three kilometers, but with the scaling factored out
s = 3 * kilo * m

# multiplication of two lengths gives an area
a = l * s
# is now 6 square kilometers

Units and quantities work intuitively and largely the same across C++ and Python, but we provide some details below.

C++

Units are defined in the units namespace, and exist at runtime, since we need to cater to dynamical language bindings. In the units::literals namespace, we find user defined literals for all units above, e.g. 10_mV. Integral powers of units are constructed using the .pow(int) member, e.g. m2 = m.pow(2). Units and quantities can be converted to and from strings using the std::string to_string(const T&) and T from_string_cast(const std::string&) functions. Conversion between different units is done like this

namespace U = arb::units;

// membrane capacitance in SI
auto c_m = 42*U::F/U::m.pow(2) // same as 42*U::F*U::m.pow(-2)
// convert to different unit and extract value
auto c_m_ = c_m.value_as(U::uF*U::cm.pow(-2))
// invalid conversions result in NaN values, so check
if (std::isnan(c_m_)) throw std::domain_error("Invalid value");

however, Arbor does this whenever values pass its interface.

class unit

Describes a unit of measurement.

pow(int)

Raise unit to integral power.

class quantity

A tuple of a value and a unit of measurement.

value_as(unit)

Convert to another unit and return converted value, possibly NaN, if malformed.

Python

Units are defined in the units sub-module. Integral powers of units are constructed using the ** operator, e.g. m2 = m ** 2. Units and quantities can be converted to a string using the str() function. Conversion between different units is done like this

from arbor import units as U
from math import isnan

# membrane capacitance in SI
c_m = 42*U.F/U.m**2
# convert to different unit and extract value
c_m_ = c_m.value_as(U.uF*U.cm**-2)
# invalid conversions result in NaN values, so check
if isnan(c_m_):
    raise ValueError("Invalid value")

however, Arbor does this whenever values pass its interface.

class arbor.units.unit

Describes a unit of measurement.

class arbor.units.quantity

A tuple of a value and a unit of measurement.

value_as()

Convert to another unit and return converted value, possibly NaN, if malformed.