xoutil.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.
This module allows you to define dimensions (or quantity types):
>>> from xoutil.dim.meta import Dimension, UNIT
>>> @Dimension.new
... class Length(object):
... 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(object):
... second = UNIT
>>> metre + Time.second
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
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(object):
... m = UNIT
>>> m = L.m # Save this
>>> # Recreate the same dimension.
>>> @Dimension.new
... class L(object):
... 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
xoutil.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 toUNIT
.
-
_unit_
¶ The canonical
quantity
. This is the quantity 1 (UNIT
) expressed in terms of the canonical unit.
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
-
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)(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.
Example:
>>> @Dimension.new(unit_alias='man') ... class Workforce(object): ... 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 beEffort.men_second
.
-
-
class
xoutil.dim.meta.
Signature
(top=None, bottom=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'
-
class
xoutil.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 xoutil.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
xoutil.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 xoutil.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
-
xoutil.dim.meta.
UNIT
¶ This the constant value
1
. It’s given this name to emphasize it’s the canonical unit for a dimension.
-
xoutil.dim.meta.
SCALAR
¶ The signature of dimensionless quantities.