xotl.tools.dim.meta – Meta-definitions for concrete numbers.

Facilities to work with concrete numbers.

A concrete number is a number associated with the things being counted, in contrast to an abstract number which is a number as a single entity.

Wikipedia

This module allows you to define dimensions (or quantity types):

>>> from xotl.tools.dim.meta import Dimension, UNIT
>>> @Dimension.new
... class Length:
...     metre = UNIT
...     kilometre = 1000 * metre
...     centimetre = metre/100
...     milimetre = milimetres = metre/1000
...
...     inch = inches = 24.5 * milimetres
...     foot = feet = 12 * inches

See also

Module base defines the standard base quantities.

Each dimension must define a single canonical unit for measuring quantities within the dimension. Values in the dimension are always expressed in terms of the canonical units.

In the previous example the dimension Length defined the metre for its canonical unit. The name of canonical unit defines the signature for the quantities in the dimension.

When printed (or repr-ed) quantities use the format <magnitude>::<signature>:

>>> metre = Length.metre
>>> metre
1::{<Length.metre>}/{}

Quantities support the standard arithmetical operations of addition, subtraction, multiplication and division. In fact, you obtain different quantities in the dimension by multiplying with the canonical unit:

>>> metre + metre
2::{<Length.metre>}/{}
>>> metre*metre
1::{<Length.metre>, <Length.metre>}/{}
>>> km = 1000 * metre
>>> 5 * km
5000::{<Length.metre>}/{}

Dimensional homogeneity imposes restrictions on the allowed operations between quantities. Only commensurable quantities (quantities of the same dimension) can be compared, equated, added, or subtracted.

>>> @Dimension.new
>>> class Time:
...     second = UNIT
>>> metre + Time.second  # doctest: +ELLIPSIS
Traceback (...)
...
OperandTypeError: unsupported operand type(s) for +:...

However, you can take ratios of incommensurable quantities (quantities with different dimensions), and multiply or divide them.

>>> metre/Time.second
>>> 1::{<Length.metre>}/{<Time.second>}

Warning

Decimal numbers are not supported.

This module makes not attempt to fix the standing incompatibility between floats and decimal.Decimal:

>>> import decimal
>>> decimal.Decimal('0') + 0.1  # doctest: +ELLIPSIS
Traceback (...)
...
TypeError: unsupported operand type(s) for +: 'Decimal' and 'float'

The signature created by Dimension for its canonical unit is simply a string that varies with the name of the dimension and that of the canonical unit. This implies that you can recreate the same dimension and it will be interoperable with the former:

>>> @Dimension.new
... class L:
...    m = UNIT

>>> m = L.m  # Save this


>>> # Recreate the same dimension.
>>> @Dimension.new
... class L:
...    m = UNIT

>>> m == L.m
True

Both the dimension name and the canonical unit name must be the same for this to work.

Note

We advice to define a dimension only once and import it where needed.

class xotl.tools.dim.meta.Dimension[source]

A type for quantities.

This is a metaclass for dimensions. Every instance (class) will automatically have the following attributes:

_unitname_

The name of canonical unit in the dimension. Notice that aliases are created after the defined canonical unit. This is the name of the attribute provided in the class definition of the dimension with value equal to UNIT.

_unit_

The canonical quantity. This is the quantity 1 (UNIT) expressed in terms of the canonical unit.

_signature_

The canonical signature of the quantities.

It’s always true that Quantity(UNIT, self._signature_) == self._unit_.

The provided dimension Length has the canonical quantity 1 metre:

>>> Length.metre
1::{<Length.metre>}/{}

>>> Length._unit_ == Length.metre == Quantity(1, Length._signature_)
True

You may subclass Dimension and customize the attribute Quantity. The value of the attribute should be a subclass of Quantity or a callable that returns instances of Quantity. You may provide it by fully-qualified name as supported by import_object().

Changed in version 2.1.0: Added class-attribute Quantity.

class Quantity(quantity, units)

Customizable Quantity factory. It must be a callable that takes a number and a signature, and returns a Quantity-like object.

classmethod new(*source, **kwargs)[source]

Define a new dimension.

This is a wrapped decorator. The actual possible signatures are:

  • new(unit_alias=None, unit_aliases=None, Quantity=None)(source)
  • new(source)

This allows to use this method as decorator with or without arguments.

Parameters:
  • source – A class with at least the canonical unit definition. Other unit definitions will be automatically converted.
  • unit_alias – An alias for the canonical unit. You cannot use a source with several canonical units. This is a simple way to introduce a single alias.
  • unit_aliases – A sequence with the name of other aliases for the canonical unit.
  • Quantity – A replacement for the default Quantity of the dimension. It defaults to None; which then looks for the Quantity attribute the class definition. This allows to provide a custom Quantity without having to subclass Dimension.

Example:

>>> @Dimension.new(unit_alias='man')
... class Workforce:
...    men = UNIT
>>> Workforce.men == Workforce.man == Workforce._unit_
True

The resulting class will be an instance of Dimension

>>> isinstance(Workforce, Dimension)
True

The original class is totally missed:

>>> Workforce.mro()
[...Workforce, object]

To complete the example, let’s introduce the dimension Effort that expresses the usual amount of men-power and time needed to complete some task. However Time has the second as it canonical unit, but the standard for Effort is men-hour:

>>> class Effort(Workforce * Time):
...    # Since the canonical unit of a composed quantity type is
...    # built from the canonical units of the operands, but the
...    # true "canonical type" of effort is usually men-hour we
...    # re-introduce it.
...    men_hour = 60

This does not mean that Effort._unit_ == Effort.men_hour. The canonical unit would be Effort.men_second.

Changed in version 2.1.0: Added keyword parameter Quantity.

class xotl.tools.dim.meta.Signature(top: Sequence[TEq] = None, bottom: Sequence[TEq] = None)[source]

The layout of the kinds that compose a quantity.

The layout is given by a pair non-ordered collections (repetition is allowed): the numerator (we call it top within the signature) and the denominator (bottom).

We represent a signature as {top elements}/{bottom elements}.

You may regard a signature as an abstract ‘syntactical’ part of a quantity. For Length, the {metre}/{} is the signature of such a quantity.

The number “10” is not tied to any particular kind of quantity. Bare numbers have no kind and the bear the signature {}/{}.

The items of top and bottom are required to be comparable for equality (==).

You can multiply and divide signatures and simplification happens automatically.

You should regard signatures as immutable values. In fact, this is kind of an internal, but interesting, concept of this module.

Examples:

>>> distance = Signature('m')
>>> distance
{m}/{}

>>> time = Signature('s')

>>> freq = 1/time
>>> freq
{}/{s}

>>> speed = distance/time
>>> speed
{m}/{s}

>>> acceleration = speed/time
>>> acceleration
{m}/{s, s}

You may compare signatures for equality.

>>> acceleration == distance/(time*Signature('s'))
True
>>> speed == distance * freq
True

Signature don’t support neither addition nor subtraction:

>>> distance + distance  
Traceback (...)
...
TypeError: unsupported operand type(s) for +: 'Signature' and 'Signature'
static simplify(top, bottom)[source]

Removes equal items from top and bottom in a one-to-one correspondence.

Signatures are simplified on initialization:

>>> Signature('abcxa', 'bxay')
{c, a}/{y}

This function takes top and bottom and returns simplified tuples for top and bottom.

class xotl.tools.dim.meta.Quantity(quantity, units)[source]

A concrete number of quantity (expressed in) units.

Parameters:
  • quantity – A real number.
  • units – A signature for the units the denominate the given quantity.

You can construct instances by operating with the attributes of a dimension. For instance, this is 5 kilometres:

>>> from xotl.tools.dim.base import L
>>> 5 * L.km
5000::{<Length.metre>}/{}

A concrete number is of the type of its dimension:

>>> isinstance(5 * L.km, L)
True
class xotl.tools.dim.meta.Scalar[source]

A quantity whose signature is always empty.

Most of the time you should not deal with this quantity. Any normal operation that results in a scalar gets reduced to Python’s type:

>>> from xotl.tools.dim.base import L
>>> L.m/L.m
1.0

This type makes the operations on dimensions closed under multiplication:

>>> Scalar * L == L == L * Scalar
True
xotl.tools.dim.meta.UNIT

This the constant value 1. It’s given this name to emphasize it’s the canonical unit for a dimension.

xotl.tools.dim.meta.SCALAR

The signature of dimensionless quantities.