{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Physical units\n", "\n", "All variables in Scipp have a physical unit.\n", "Variables are used for coordinates, data, and attributes, therefore, all of these have a unit.\n", "\n", "## Basic Operations\n", "\n", "Units are encoded by the [scipp.Unit](../generated/classes/scipp.Unit.rst) class.\n", "Instances of this class can be constructed from strings:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import scipp as sc\n", "\n", "length = sc.Unit('m')\n", "length" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[scipp.Unit](../generated/classes/scipp.Unit.rst) defines mathematical operators for combining units:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "area = length * length\n", "area" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "volume = length * length * length\n", "volume" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "also_volume = length**3\n", "also_volume" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('dimensionless') / length" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "speed = length / sc.Unit('s')\n", "speed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Invalid operations raise exceptions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "speed + length" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is also possible to construct composite units directly from strings:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('km')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('m/s')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('counts')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('kg*m^2/s^2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For convenience, the [scipp.units](../generated/modules/scipp.units.rst) module provides some frequently used units.\n", "See [scipp.units](../generated/modules/scipp.units.rst) for a list of those units." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.kg" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.m / sc.units.s" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.dimensionless" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use `repr` to see a unit in terms of SI (plus extensions) base units:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repr(sc.Unit('V/L'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is especially helpful when it is unclear what a particular unit represents." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constructing Variables with Units\n", "\n", "[Variables](../generated/classes/scipp.Variable.rst#scipp.Variable) with units can be constructed using the `units` argument in the constructor or in [creation functions](./creation-functions.rst).\n", "When not specified explicitly, the unit of a variable usually (see below) defaults to `dimensionless` (a.k.a. `one`).\n", "That is, the variable is considered dimensionless in terms of units (not to be confused with array dimensions)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# same as sc.array(dims=['x'], values=[1, 2])\n", "# and sc.array(dims=['x'], values=[1, 2], unit='dimensionless')\n", "sc.array(dims=['x'], values=[1, 2], unit='one')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.array(dims=['x'], values=[1, 2], unit='m')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.array(dims=['x'], values=[1, 2], unit=sc.units.m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.arange('x', 0, 3, unit=sc.units.s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scalars can also be constructed using multiplication or division of a number and a unit (in addition to [scipp.scalar](../generated/functions/scipp.scalar.rst#scipp.scalar)):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "1.2 * sc.Unit('kg/m^3')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "3.4 / sc.units.K" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variables Without Units\n", "\n", "It is not always meaningful to assign a unit to a variable.\n", "For example, what is the unit of a string or a truth value?\n", "For this reason, Scipp allows variables to have no unit by setting `unit=None`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "tags": [] }, "outputs": [], "source": [ "sc.array(dims=['x'], values=[2, 4, 6], unit=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For non-numeric dtypes, the unit defaults to `None`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "sc.array(dims=['x'], values=[False, True, False])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "sc.scalar('a string')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Indices are also non-physical quantities, so they should typically be defined without a unit, too.\n", "To help with this, Scipp provides `sc.index`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "sc.index(123)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Variables without units can interoperate like variables with units.\n", "But the two groups cannot be combined:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "sc.scalar(1, unit='one') * sc.scalar(2, unit=None)" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Supported Units\n", "\n", "Scipp supports a great number of units through LLNL's [Units](https://units.readthedocs.io/en/latest/index.html) library.\n", "See in particular [Defined Units](https://units.readthedocs.io/en/latest/user-guide/defined_units.html).\n", "\n", "
\n", " INFO\n", "\n", "The LLNL/Units library is considered an implementation detail of Scipp.\n", "Using SI units is safe but other unit systems should be used with discretion.\n", "This applies especially to non-standard units like LLNL/Unit's custom (counting) units.\n", "
\n", "\n", "### Base Units\n", "All SI base units are supported with the following names:\n", "\n", "| Name | Unit |\n", "|-------|----------|\n", "| 'm' | meter |\n", "| 's' | second |\n", "| 'kg' | kilogram |\n", "| 'K' | kelvin |\n", "| 'A' | ampere |\n", "| 'mol' | mole |\n", "| 'cd' | candela |\n", "\n", "In addition, the following base units are supported for cases not covered by SI.\n", "\n", "| name | Unit |\n", "|---------|------------------------|\n", "| 'rad' | radian |\n", "| 'count' | single object counting |\n", "\n", "### Derived units\n", "Many derived units can also be specified as arguments to `sc.Unit`.\n", "Some examples are\n", "\n", "| Name | Unit |\n", "|------------------|---------------|\n", "| 'Hz' | hertz |\n", "| 'J' | joule |\n", "| 'V' | volt |\n", "| 'W' | watt |\n", "| 'angstrom' / 'Å' | ångström |\n", "| 'eV' | electron volt |\n", "| 'L' | liter |\n", "| 'min' | minute |\n", "| 'D' / 'day' | day |\n", "\n", "Units can be modified with SI prefixes, for instance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\n", " sc.Unit('mm'),\n", " sc.Unit('microsecond'),\n", " sc.Unit('micro s'),\n", " sc.Unit('us'),\n", " sc.Unit('MJ'),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also specify exponents for units or exponentiate the `Unit` object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(sc.Unit('m^2'), sc.Unit('m**2'), sc.Unit('m') ** 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conversion Between Units of Different Scales\n", "\n", "Data can be converted between compatible units using [sc.to_unit](../generated/functions/scipp.to_unit.rst#scipp.to_unit).\n", "Only conversions between units of the same physical dimensions are possible." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.to_unit(1.0 * sc.units.m, 'mm')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.to_unit(1.0 * sc.Unit('parsec'), 'm')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.to_unit(3.14 * sc.Unit('m/s'), 'km/h')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "sc.to_unit(1.0 * sc.Unit('s'), 'm')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit Aliases\n", "\n", "It is possible to define custom aliases for units.\n", "This can be used to\n", "\n", "- guide string formatting to prefer a certain unit, e.g. angstrom over nm\n", "- define domain specific units that can be expressed in terms of other units to\n", " - guide string formatting\n", " - construct units from strings with custom names\n", " \n", "### Prioritizing Units in String Formatting\n", "\n", "When dealing with crystals or molecules, it is often convenient to use angstrom as a unit.\n", "But by default, string formatting tends to prefer different bases in composite units.\n", "For example" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('us/angstrom**2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This result is not very useful.\n", "We can make the formatter prefer angstrom by defining it as an alias of itself (or alternatively of `'10^-10 m'`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.aliases['angstrom'] = 'angstrom'\n", "sc.Unit('us/angstrom**2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that 'angstrom' is predefined and simple units involving it, such as `sc.Unit('angstrom/s')` are formatted properly without the alias.\n", "But the alias improves more complicated cases like the one above.\n", "\n", "Aliases are global and stay in effect until they are removed.\n", "This can be done using `sc.units.aliases.clear()` to remove all aliases or, to remove only one, using" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del sc.units.aliases['angstrom']\n", "sc.Unit('us/angstrom**2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, a context manager can be used to remove aliases automatically:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with sc.units.aliases.scoped(angstrom='angstrom'):\n", " print(sc.Unit('us/angstrom**2'))\n", "print(sc.Unit('us/angstrom**2'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But note that the context manager uses the global alias table and affects code outside of the context.\n", "Details are explained in [scipp.units.aliases.scoped](../generated/modules/scipp.units.UnitAliases.rst#scipp.units.UnitAliases.scoped).\n", "\n", "See [scipp.units.UnitAliases](../generated/modules/scipp.units.UnitAliases.rst) for the full API of `sc.units.aliases`.\n", "\n", "### Defining New Units\n", "\n", "It is possible to define completely new units as aliases as long as they can be expressed in terms of other units.\n", "For example, define an speed unit:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.aliases['speed'] = 'm/s'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In practice, a unit alias might refer to a characteristic speed, length, or time in a concrete physical system.\n", "Or it might refer to a customary unit in a scientific field.\n", "\n", "After defining the alias, the string formatter prioritizes 'speed' over 'm/s' when appropriate:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(sc.Unit('m/s'))\n", "print(sc.Unit('km/s'))\n", "print(sc.Unit('kg*m/s'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we can also construct units using 'speed' as an argument:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(sc.Unit('speed'))\n", "print(sc.Unit('kg*mspeed**2'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This also works in variables:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.scalar(4, unit='speed')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using `repr`, we can see that 'speed' is not a fundamentally new unit but simply expressed in terms of 'm' and 's':" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repr(sc.Unit('speed'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Defining Scaled Units\n", "\n", "In the previous example, 'speed' was defined as a combination of 'm' and 's'.\n", "It is also possible to define aliases of scaled units.\n", "Examples of scaled units are millisecond, hour, or the previously used angstrom.\n", "These units work by encoding a scale factor ('multiplier') in the unit.\n", "This multiplier can be any floating point number, so for example, we can define a dog year as 52 days or 4492800 seconds:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.aliases['dogyear'] = sc.scalar(4492800, unit='s')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can use 'dogyear' as a unit:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('dogyear')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that the above is indeed a scaled unit by using `repr`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repr(sc.Unit('dogyear'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, we can specify the multiplier in the unit directly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.Unit('4492800s') == sc.Unit('dogyear')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use this to define the same alias as before but without going through a Scipp variable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sc.units.aliases.clear()\n", "sc.units.aliases['dogyear'] = '4492800s'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "repr(sc.Unit('dogyear'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the unit multiplier is not the same as a value in a variable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "var = sc.scalar(2, unit='dogyear')\n", "var" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Removing the alias reveals the multiplier (hover the mouse over the unit if it is abbreviated):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del sc.units.aliases['dogyear']\n", "var" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The multiplier can be moved into the value of the variable by converting to seconds:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "var.to(unit='s')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Conversion also works the other way around:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "sc.units.aliases['dogyear'] = '4492800s'\n", "sc.scalar(8985600, unit='s').to(unit='dogyear')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.15" } }, "nbformat": 4, "nbformat_minor": 4 }