# Physical units#

All variables in Scipp have a physical unit. Variables are used for coordinates, data, and attributes, therefore, all of these have a unit.

## Basic Operations#

Units are encoded by the scipp.Unit class. Instances of this class can be constructed from strings:

[1]:

import scipp as sc

length = sc.Unit('m')
length

[1]:

m

scipp.Unit defines mathematical operators for combining units:

[2]:

area = length * length
area

[2]:

m^2
[3]:

volume = length * length * length
volume

[3]:

m^3
[4]:

also_volume = length ** 3
also_volume

[4]:

m^3
[5]:

sc.Unit('dimensionless') / length

[5]:

1/m
[6]:

speed = length / sc.Unit('s')
speed

[6]:

m/s

Invalid operations raise exceptions:

[7]:

speed + length

---------------------------------------------------------------------------
UnitError                                 Traceback (most recent call last)
Cell In [7], line 1
----> 1 speed + length

UnitError: Cannot add m/s and m.


It is also possible to construct composite units directly from strings:

[8]:

sc.Unit('km')

[8]:

km
[9]:

sc.Unit('m/s')

[9]:

m/s
[10]:

sc.Unit('counts')

[10]:

counts
[11]:

sc.Unit('kg*m^2/s^2')

[11]:

J

For convenience, the scipp.units module provides some frequently used units. See scipp.units for a list of those units.

[12]:

sc.units.kg

[12]:

kg
[13]:

sc.units.m / sc.units.s

[13]:

m/s
[14]:

sc.units.dimensionless

[14]:

dimensionless

Use repr to see a unit in terms of SI (plus extensions) base units:

[15]:

repr(sc.Unit('V/L'))

[15]:

'Unit(1000*m**-1*kg**1*s**-3*A**-1)'


This is especially helpful when it is unclear what a particular unit represents.

## Constructing Variables with Units#

Variables with units can be constructed using the units argument in the constructor or in creation functions. When not specified explicitly, the unit of a variable defaults to dimensionless (a.k.a. one). That is, the variable is considered dimensionless in terms of units (not to be confused with array dimensions).

[16]:

# same as sc.Variable(dims=['x'], values=[1, 2])
# and     sc.Variable(dims=['x'], values=[1, 2], unit='dimensionless')
sc.Variable(dims=['x'], values=[1, 2], unit='one')

[16]:

scipp.Variable (272 Bytes)
• (x: 2)
int64
𝟙
1, 2
Values:array([1, 2])
[17]:

sc.Variable(dims=['x'], values=[1, 2], unit='m')

[17]:

scipp.Variable (272 Bytes)
• (x: 2)
int64
m
1, 2
Values:array([1, 2])
[18]:

sc.Variable(dims=['x'], values=[1, 2], unit=sc.units.m)

[18]:

scipp.Variable (272 Bytes)
• (x: 2)
int64
m
1, 2
Values:array([1, 2])
[19]:

sc.arange('x', 0, 3, unit=sc.units.s)

[19]:

scipp.Variable (280 Bytes)
• (x: 3)
int64
s
0, 1, 2
Values:array([0, 1, 2])

Scalars can also be constructed using multiplication or division of a number and a unit (in addition to scipp.scalar):

[20]:

1.2 * sc.Unit('kg/m^3')

[20]:

scipp.Variable (264 Bytes)
• ()
float64
kg/m^3
1.2
Values:array(1.2)
[21]:

3.4 / sc.units.K

[21]:

scipp.Variable (264 Bytes)
• ()
float64
1/K
3.4
Values:array(3.4)

## Supported Units#

Scipp supports a great number of units through LLNL’s Units library. See in particular Defined Units.

INFO

The LLNL/Units library is considered an implementation detail of Scipp. Using SI units is safe but other unit systems should be used with discretion. This applies especially to non-standard units like LLNL/Unit’s custom (counting) units.

### Base Units#

All SI base units are supported with the following names:

Name

Unit

‘m’

meter

‘s’

second

‘kg’

kilogram

‘K’

kelvin

‘A’

ampere

‘mol’

mole

‘cd’

candela

In addition, the following base units are supported for cases not covered by SI.

name

Unit

‘count’

single object counting

### Derived units#

Many derived units can also be specified as arguments to sc.Unit. Some examples are

Name

Unit

‘Hz’

hertz

‘J’

joule

‘V’

volt

‘W’

watt

‘angstrom’ / ‘Å’

ångström

‘eV’

electron volt

‘L’

liter

‘min’

minute

‘D’ / ‘day’

day

Units can be modified with SI prefixes, for instance

[22]:

print(sc.Unit('mm'), sc.Unit('microsecond'),
sc.Unit('micro s'), sc.Unit('us'), sc.Unit('MJ'))

mm µs µs µs MJ


You can also specify exponents for units or exponentiate the Unit object:

[23]:

print(sc.Unit('m^2'), sc.Unit('m**2'), sc.Unit('m')**2)

m^2 m^2 m^2


## Conversion Between Units of Different Scales#

Data can be converted between compatible units using sc.to_unit. Only conversions between units of the same physical dimensions are possible.

[24]:

sc.to_unit(1.0 * sc.units.m, 'mm')

[24]:

scipp.Variable (264 Bytes)
• ()
float64
mm
1000.0
Values:array(1000.)
[25]:

sc.to_unit(1.0 * sc.Unit('parsec'), 'm')

[25]:

scipp.Variable (264 Bytes)
• ()
float64
m
3.085678e+16
Values:array(3.085678e+16)
[26]:

sc.to_unit(3.14 * sc.Unit('m/s'), 'km/h')

[26]:

scipp.Variable (264 Bytes)
• ()
float64
km/hr
11.303999999999998
Values:array(11.304)
[27]:

sc.to_unit(1.0 * sc.Unit('s'), 'm')

---------------------------------------------------------------------------
UnitError                                 Traceback (most recent call last)
Cell In [27], line 1
----> 1 sc.to_unit(1.0 * sc.Unit('s'), 'm')

File ~/work/scipp/scipp/.tox/docs/lib/python3.8/site-packages/scipp/core/unary.py:70, in to_unit(x, unit, copy)
35 def to_unit(x: _cpp.Variable,
36             unit: _Union[_cpp.Unit, str],
37             *,
38             copy: bool = True) -> _cpp.Variable:
39     """Convert the variable to a different unit.
40
41     Raises a :class:scipp.UnitError if the input unit is not compatible
(...)
68       <scipp.Variable> ()    float64             [mm]  [1200]
69     """
---> 70     return _call_cpp_func(_cpp.to_unit, x=x, unit=unit, copy=copy)

File ~/work/scipp/scipp/.tox/docs/lib/python3.8/site-packages/scipp/core/_cpp_wrapper_util.py:8, in call_func(func, out, *args, **kwargs)
6 def call_func(func, *args, out=None, **kwargs):
7     if out is None:
----> 8         return func(*args, **kwargs)
9     else:
10         return func(*args, **kwargs, out=out)

UnitError: Conversion from s to m is not valid.


## Unit Aliases#

It is possible to define custom aliases for units. This can be used to

• guide string formatting to prefer a certain unit, e.g. angstrom over nm

• define domain specific units that can be expressed in terms of other units to

• guide string formatting

• construct units from strings with custom names

### Prioritizing Units in String Formatting#

When dealing with crystals or molecules, it is often convenient to use angstrom as a unit. But by default, string formatting tends to prefer different bases in composite units. For example

[28]:

sc.Unit('us/angstrom**2')

[28]:

9290304000000s/ft^2

This result is not very useful. We can make the formatter prefer angstrom by defining it as an alias of itself (or alternatively of '10^-10 m'):

[29]:

sc.units.aliases['angstrom'] = 'angstrom'
sc.Unit('us/angstrom**2')

[29]:

µs/angstrom^2

Note that ‘angstrom’ is predefined and simple units involving it, such as sc.Unit('angstrom/s') are formatted properly without the alias. But the alias improves more complicated cases like the one above.

Aliases are global and stay in effect until they are removed. This can be done using sc.units.aliases.clear() to remove all aliases or, to remove only one, using

[30]:

del sc.units.aliases['angstrom']
sc.Unit('us/angstrom**2')

[30]:

9290304000000s/ft^2

Alternatively, a context manager can be used to remove aliases automatically:

[31]:

with sc.units.aliases.scoped(angstrom='angstrom'):
print(sc.Unit('us/angstrom**2'))
print(sc.Unit('us/angstrom**2'))

µs/angstrom^2
9290304000000s/ft^2


But note that the context manager uses the global alias table and affects code outside of the context. Details are explained in scipp.units.aliases.scoped.

See scipp.units.UnitAliases for the full API of sc.units.aliases.

### Defining New Units#

It is possible to define completely new units as aliases as long as they can be expressed in terms of other units. For example, define an speed unit:

[32]:

sc.units.aliases['speed'] = 'm/s'


In practice, a unit alias might refer to a characteristic speed, length, or time in a concrete physical system. Or it might refer to a customary unit in a scientific field.

After defining the alias, the string formatter prioritizes ‘speed’ over ‘m/s’ when appropriate:

[33]:

print(sc.Unit('m/s'))
print(sc.Unit('km/s'))
print(sc.Unit('kg*m/s'))

speed
kspeed
N*s


And we can also construct units using ‘speed’ as an argument:

[34]:

print(sc.Unit('speed'))
print(sc.Unit('kg*mspeed**2'))

speed
µJ


This also works in variables:

[35]:

sc.scalar(4, unit='speed')

[35]:

scipp.Variable (264 Bytes)
• ()
int64
speed
4
Values:array(4)

Using repr, we can see that ‘speed’ is not a fundamentally new unit but simply expressed in terms of ‘m’ and ‘s’:

[36]:

repr(sc.Unit('speed'))

[36]:

'Unit(1*m**1*s**-1)'


### Defining Scaled Units#

In the previous example, ‘speed’ was defined as a combination of ‘m’ and ‘s’. It is also possible to define aliases of scaled units. Examples of scaled units are millisecond, hour, or the previously used angstrom. These units work by encoding a scale factor (‘multiplier’) in the unit. This multiplier can be any floating point number, so for example, we can define a dog year as 52 days or 4492800 seconds:

[37]:

sc.units.aliases['dogyear'] = sc.scalar(4492800, unit='s')


Now we can use ‘dogyear’ as a unit:

[38]:

sc.Unit('dogyear')

[38]:

dogyear

We can see that the above is indeed a scaled unit by using repr:

[39]:

repr(sc.Unit('dogyear'))

[39]:

'Unit(4.4928e+06*s**1)'


Alternatively, we can specify the multiplier in the unit directly:

[40]:

sc.Unit('4492800s') == sc.Unit('dogyear')

[40]:

True


We can use this to define the same alias as before but without going through a Scipp variable:

[41]:

sc.units.aliases.clear()
sc.units.aliases['dogyear'] = '4492800s'

[42]:

repr(sc.Unit('dogyear'))

[42]:

'Unit(4.4928e+06*s**1)'


Note that the unit multiplier is not the same as a value in a variable:

[43]:

var = sc.scalar(2, unit='dogyear')
var

[43]:

scipp.Variable (264 Bytes)
• ()
int64
dogyear
2
Values:array(2)

Removing the alias reveals the multiplier (hover the mouse over the unit if it is abbreviated):

[44]:

del sc.units.aliases['dogyear']
var

[44]:

scipp.Variable (264 Bytes)
• ()
int64
4492800s
2
Values:array(2)

The multiplier can be moved into the value of the variable by converting to seconds:

[45]:

var.to(unit='s')

[45]:

scipp.Variable (264 Bytes)
• ()
int64
s
8985600
Values:array(8985600)

Conversion also works the other way around:

[46]:

sc.units.aliases['dogyear'] = '4492800s'
sc.scalar(8985600, unit='s').to(unit='dogyear')

[46]:

scipp.Variable (264 Bytes)
• ()
int64
dogyear
2
Values:array(2)