"""
pint.registry
~~~~~~~~~~~~~
Defines the UnitRegistry, a class to contain units and their relations.
This registry contains all pint capabilities, but you can build your
customized registry by picking only the features that you actually
need.
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
# pyright: reportNoOverloadImplementation=none
# pyright: reportInvalidTypeArguments=warning
from __future__ import annotations
from collections.abc import Iterator, Sequence
from typing import (
TYPE_CHECKING,
Any,
Generic,
Self,
TypeAlias,
overload,
override,
)
from typing_extensions import TypeVar
from . import facets, registry_helpers
from .util import logger, pi_theorem
if TYPE_CHECKING:
import datetime
import numpy as np
import optype as opt
import optype.numpy as npt
from ._typing import Magnitude, Scalar, UnitLike
from ._typing import Quantity as _Quantity
from ._typing import Unit as _Unit
from .facets.plain.quantity import PlainQuantity as _PlainQuantity
else:
_PlainQuantity = facets.PlainRegistry.Quantity
# To build the Quantity and Unit classes
# we follow the UnitRegistry bases
# but
MagnitudeT_co = TypeVar(
"MagnitudeT_co", covariant=True, bound="Magnitude", default="Any"
)
MagnitudeT = TypeVar("MagnitudeT", bound="Magnitude", default="Any")
class Quantity(
facets.SystemRegistry.Quantity[MagnitudeT_co],
facets.ContextRegistry.Quantity[MagnitudeT_co],
facets.DaskRegistry.Quantity[MagnitudeT_co],
facets.NumpyRegistry.Quantity[MagnitudeT_co],
facets.MeasurementRegistry.Quantity[MagnitudeT_co],
facets.NonMultiplicativeRegistry.Quantity[MagnitudeT_co],
_PlainQuantity[MagnitudeT_co],
Generic[MagnitudeT_co],
):
if TYPE_CHECKING:
# NOTE: This list of method signatures must be kept in sync with PlainQuantity's overloads
# (you can find the class in pint/facets/plain/quantity.py)
@overload
def __new__(
cls,
value: datetime.timedelta | np.timedelta64,
units: UnitLike | None = None,
) -> "Quantity[float]": ...
@overload
def __new__(
cls, value: str, units: UnitLike | None = None
) -> "Quantity[Any]": ...
@overload # FIXME: This could be more precise
def __new__[ScalarT: Scalar]( # type: ignore[misc]
cls, value: Sequence[ScalarT], units: UnitLike | None = None
) -> "Quantity[npt.Array1D]": ...
@overload # This overload allows for `Quantity[Any](<T>)` to return `Quantity[T]`.
def __new__[T: Magnitude](
cls,
value: T,
units: UnitLike | None = None,
) -> "Quantity[T]": ...
@overload
def __new__(cls, value: Self, units: UnitLike | None = None) -> Self: ...
@override
def __iter__[T: Magnitude](
self: Quantity[opt.CanIter[T]],
) -> Iterator[Quantity[T]]: ...
@overload
def __round__[T: Magnitude](
self: Quantity[opt.CanRound1[T]], ndigits: None = None
) -> Quantity[T]: ...
@overload
def __round__[T: Magnitude](
self: Quantity[opt.CanRound2[int, T]], ndigits: int
) -> Quantity[T]: ...
@override
def __abs__[T: Magnitude](self: Quantity[opt.CanAbs[T]]) -> Quantity[T]: ...
@classmethod
@override
def from_list[T: np.floating | np.integer](
cls: type[Quantity[opt.numpy.Array1D[np.float64]]],
quant_list: list[
Quantity[np.floating]
| Quantity[np.integer]
| Quantity[float]
| Quantity[int]
],
units: UnitLike | None = None,
) -> Quantity[opt.numpy.Array1D[np.float64]]: ...
@classmethod
@override
def from_sequence[T: np.number](
cls: type[Quantity[opt.numpy.Array1D[np.float64]]],
seq: Sequence[
Quantity[np.floating]
| Quantity[np.integer]
| Quantity[float]
| Quantity[int]
],
units: UnitLike | None = None,
) -> Quantity[opt.numpy.Array1D[np.float64]]: ...
@overload
def tolist[T: opt.numpy.Array0D | np.number](
self: Quantity[T],
) -> Quantity[T]: ...
@overload
def tolist[X: np.number](
self: Quantity[opt.numpy.Array1D[X]],
) -> list[Quantity[X]]: ...
@overload
def tolist[X: np.number](
self: Quantity[opt.numpy.Array2D[X]],
) -> list[list[Quantity[X]]]: ...
@overload
def tolist[X: np.number](
self: Quantity[opt.numpy.Array3D[X]],
) -> list[list[list[Quantity[X]]]]: ...
@overload
def tolist[X: np.number](
self: Quantity[
opt.numpy.Array[tuple[int, int, int, int, *tuple[int, ...]], X]
],
) -> list[list[list[list[Any]]]]: ...
# Quantity[float] += datetime -> becomes datetime
@overload
def __iadd__(
self: Quantity[int | float], other: datetime.datetime
) -> datetime.datetime: ...
# General overload (should work in most cases)
@overload
def __iadd__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanIAdd[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
# Quantity[float] + datetime -> datetime
@overload
def __add__(
self: Quantity[int | float], other: datetime.datetime
) -> datetime.datetime: ...
# Quantity[float | array[float]] + timedelta -> Quantity[float | array[float]]
@overload
def __add__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads (should work in most cases)
@overload
def __add__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanAdd[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __add__[U: Magnitude](
self,
other: Quantity[opt.CanRAdd[MagnitudeT_co, U]]
| opt.CanRAdd[MagnitudeT_co, U],
) -> Quantity[U]: ...
def __isub__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanISub[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
# Quantity[float | array[float]] - timedelta -> Quantity[float | array[float]]
@overload
def __sub__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads (should work in most cases)
@overload
def __sub__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanSub[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __sub__[U: Magnitude](
self,
other: Quantity[opt.CanRSub[MagnitudeT_co, U]]
| opt.CanRSub[MagnitudeT_co, U],
) -> Quantity[U]: ...
# datetime - Quantity[float] -> datetime
@overload
def __rsub__(
self: Quantity[int | float],
other: datetime.datetime,
) -> datetime.datetime: ...
# timedelta - Quantity[float | array[float]] -> Quantity[float | array[float]]
@overload
def __rsub__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads (should work in most cases)
@overload
def __rsub__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanRSub[T, U]], other: T
) -> Quantity[U]: ...
@overload
def __rsub__[U: Magnitude](
self,
other: opt.CanSub[MagnitudeT_co, U],
) -> Quantity[U]: ...
@override
def __imul__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanIMul[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
# Quantity[float | array[float]] * timedelta -> Quantity[float | array[float]]
@overload
def __mul__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads (should work in most cases)
@overload
def __mul__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanMul[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __mul__[U: Magnitude](
self,
other: Quantity[opt.CanRMul[MagnitudeT_co, U]]
| opt.CanRMul[MagnitudeT_co, U],
) -> Quantity[U]: ...
__rmul__ = __mul__
@overload
def __matmul__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanMatmul[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __matmul__[U: Magnitude](
self,
other: Quantity[opt.CanRMatmul[MagnitudeT_co, U]]
| opt.CanRMatmul[MagnitudeT_co, U],
) -> Quantity[U]: ...
__rmatmul__ = __matmul__
@override
def __itruediv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanITruediv[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
# Quantity[float | array[float]] / timedelta -> Quantity[float | array[float]]
@overload
def __truediv__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads
@overload
def __truediv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanTruediv[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __truediv__[U: Magnitude](
self,
other: Quantity[opt.CanRTruediv[MagnitudeT_co, U]]
| opt.CanRTruediv[MagnitudeT_co, U],
) -> Quantity[U]: ...
# timedelta / Quantity[float | array[float]] -> Quantity[float | array[float]]
@overload
def __rtruediv__[T: int | float | npt.ArrayND[np.floating]](
self: Quantity[T], other: datetime.timedelta | np.timedelta64
) -> Quantity[T]: ...
# General overloads
@overload
def __rtruediv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanRTruediv[T, U]], other: T
) -> Quantity[U]: ...
@overload
def __rtruediv__[U: Magnitude](
self,
other: opt.CanTruediv[MagnitudeT_co, U],
) -> Quantity[U]: ...
@override
def __ifloordiv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanIFloordiv[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __floordiv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanFloordiv[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __floordiv__[U: Magnitude](
self,
other: Quantity[opt.CanRFloordiv[MagnitudeT_co, U]]
| opt.CanRTruediv[MagnitudeT_co, U],
) -> Quantity[U]: ...
@overload
def __rfloordiv__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanRFloordiv[T, U]], other: T
) -> Quantity[U]: ...
@overload
def __rfloordiv__[U: Magnitude](
self,
other: opt.CanFloordiv[MagnitudeT_co, U],
) -> Quantity[U]: ...
@override
def __imod__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanIMod[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __mod__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanMod[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __mod__[U: Magnitude](
self,
other: Quantity[opt.CanRMod[MagnitudeT_co, U]]
| opt.CanRMod[MagnitudeT_co, U],
) -> Quantity[U]: ...
@overload
def __rmod__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanRMod[T, U]], other: T
) -> Quantity[U]: ...
@overload
def __rmod__[U: Magnitude](
self,
other: opt.CanMod[MagnitudeT_co, U],
) -> Quantity[U]: ...
@override
def __ipow__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanIPow[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __pow__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanPow2[T, U]], other: Quantity[T] | T
) -> Quantity[U]: ...
@overload
def __pow__[U: Magnitude](
self,
other: Quantity[opt.CanRPow[MagnitudeT_co, U]]
| opt.CanRPow[MagnitudeT_co, U],
) -> Quantity[U]: ...
@overload
def __rpow__[T: Magnitude, U: Magnitude](
self: Quantity[opt.CanRPow[T, U]], other: T
) -> Quantity[U]: ...
@overload
def __rpow__[U: Magnitude](
self,
other: opt.CanPow[MagnitudeT_co, U],
) -> Quantity[U]: ...
@overload
def __divmod__[T: Magnitude, U1: Magnitude, U2: Magnitude](
self: Quantity[opt.CanDivmod[T, tuple[U1, U2]]],
other: Quantity[T] | T,
) -> tuple[Quantity[U1], Quantity[U2]]: ...
@overload
def __divmod__[U1: Magnitude, U2: Magnitude](
self,
other: Quantity[opt.CanRDivmod[MagnitudeT_co, tuple[U1, U2]]]
| opt.CanRDivmod[MagnitudeT_co, tuple[U1, U2]],
) -> tuple[Quantity[U1], Quantity[U2]]: ...
@overload
def __rdivmod__[T: Magnitude, U1: Magnitude, U2: Magnitude](
self: Quantity[opt.CanRDivmod[T, tuple[U1, U2]]], other: T
) -> tuple[Quantity[U1], Quantity[U2]]: ...
@overload
def __rdivmod__[U1: Magnitude, U2: Magnitude](
self, other: opt.CanDivmod[MagnitudeT_co, tuple[U1, U2]]
) -> tuple[Quantity[U1], Quantity[U2]]: ...
[docs]
class Unit(
facets.SystemRegistry.Unit,
facets.ContextRegistry.Unit,
facets.DaskRegistry.Unit,
facets.NumpyRegistry.Unit,
facets.MeasurementRegistry.Unit,
facets.NonMultiplicativeRegistry.Unit,
facets.PlainRegistry.Unit,
):
if TYPE_CHECKING:
# Unit * Unit -> Unit
@overload
def __mul__(self, other: Self) -> Self: ...
# Unit * timedelta -> Quantity[float]
@overload
def __mul__(
self, other: datetime.timedelta | np.timedelta64
) -> Quantity[float]: ...
# Unit * <Magnitude> -> Quantity[<Magnitude>]
@overload
def __mul__[T: Magnitude](self, other: T) -> Quantity[T]: ...
# Unit * str -> Quantity
@overload
def __mul__(self, other: str) -> Quantity[Any]: ...
__rmul__ = __mul__
[docs]
class GenericUnitRegistry[QuantityT: _Quantity, UnitT: _Unit](
facets.GenericSystemRegistry[QuantityT, UnitT],
facets.GenericContextRegistry[QuantityT, UnitT],
facets.GenericDaskRegistry[QuantityT, UnitT],
facets.GenericNumpyRegistry[QuantityT, UnitT],
facets.GenericMeasurementRegistry[QuantityT, UnitT],
facets.GenericNonMultiplicativeRegistry[QuantityT, UnitT],
facets.GenericPlainRegistry[QuantityT, UnitT],
):
pass
[docs]
class UnitRegistry(
GenericUnitRegistry[Quantity[MagnitudeT], Unit], Generic[MagnitudeT]
):
"""The unit registry stores the definitions and relationships between units.
Parameters
----------
filename :
path of the units definition file to load or line-iterable object.
Empty string to load the default definition file. (default)
None to leave the UnitRegistry empty.
force_ndarray : bool
convert any input, scalar or not to a numpy.ndarray.
(Default: False)
force_ndarray_like : bool
convert all inputs other than duck arrays to a numpy.ndarray.
(Default: False)
default_as_delta :
In the context of a multiplication of units, interpret
non-multiplicative units as their *delta* counterparts.
(Default: False)
autoconvert_offset_to_baseunit :
If True converts offset units in quantities are
converted to their plain units in multiplicative
context. If False no conversion happens. (Default: False)
on_redefinition : str
action to take in case a unit is redefined.
'warn', 'raise', 'ignore' (Default: 'raise')
auto_reduce_dimensions :
If True, reduce dimensionality on appropriate operations.
(Default: False)
autoconvert_to_preferred :
If True, converts preferred units on appropriate operations.
(Default: False)
preprocessors :
list of callables which are iteratively ran on any input expression
or unit string or None for no preprocessor.
(Default=None)
fmt_locale :
locale identifier string, used in `format_babel` or None.
(Default=None)
case_sensitive : bool, optional
Control default case sensitivity of unit parsing. (Default: True)
cache_folder : str or pathlib.Path or None, optional
Specify the folder in which cache files are saved and loaded from.
If None, the cache is disabled. (default)
"""
Quantity: TypeAlias = Quantity
Unit: TypeAlias = Unit
def __init__(
self,
filename="",
force_ndarray: bool = False,
force_ndarray_like: bool = False,
default_as_delta: bool = True,
autoconvert_offset_to_baseunit: bool = False,
on_redefinition: str = "warn",
system=None,
auto_reduce_dimensions=False,
autoconvert_to_preferred=False,
preprocessors=None,
fmt_locale=None,
non_int_type=float,
case_sensitive: bool = True,
cache_folder=None,
):
super().__init__(
filename=filename,
force_ndarray=force_ndarray,
force_ndarray_like=force_ndarray_like,
on_redefinition=on_redefinition,
default_as_delta=default_as_delta,
autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit,
system=system,
auto_reduce_dimensions=auto_reduce_dimensions,
autoconvert_to_preferred=autoconvert_to_preferred,
preprocessors=preprocessors,
fmt_locale=fmt_locale,
non_int_type=non_int_type,
case_sensitive=case_sensitive,
cache_folder=cache_folder,
)
[docs]
def pi_theorem(self, quantities):
"""Builds dimensionless quantities using the Buckingham π theorem
Parameters
----------
quantities : dict
mapping between variable name and units
Returns
-------
list
a list of dimensionless quantities expressed as dicts
"""
return pi_theorem(quantities, self)
[docs]
def setup_matplotlib(self, enable: bool = True) -> None:
"""Set up handlers for matplotlib's unit support.
Parameters
----------
enable : bool
whether support should be enabled or disabled (Default value = True)
"""
# Delays importing matplotlib until it's actually requested
from .matplotlib import setup_matplotlib_handlers
setup_matplotlib_handlers(self, enable)
wraps = registry_helpers.wraps
check = registry_helpers.check
[docs]
class LazyRegistry[QuantityT: Quantity, UnitT: Unit]:
def __init__(self, args=None, kwargs=None):
self.__dict__["params"] = args or (), kwargs or {}
def __init(self):
args, kwargs = self.__dict__["params"]
kwargs["on_redefinition"] = "raise"
self.__class__ = UnitRegistry
self.__init__(*args, **kwargs)
self._after_init()
def __getattr__(self, item):
if item == "_on_redefinition":
return "raise"
self.__init()
return getattr(self, item)
def __setattr__(self, key, value):
if key == "__class__":
super().__setattr__(key, value)
else:
self.__init()
setattr(self, key, value)
def __getitem__(self, item):
self.__init()
return self[item]
def __call__(self, *args, **kwargs):
self.__init()
return self(*args, **kwargs)
[docs]
class ApplicationRegistry:
"""A wrapper class used to distribute changes to the application registry."""
__slots__ = ["_registry"]
def __init__(self, registry):
self._registry = registry
[docs]
def get(self):
"""Get the wrapped registry"""
return self._registry
[docs]
def set(self, new_registry):
"""Set the new registry
Parameters
----------
new_registry : ApplicationRegistry or LazyRegistry or UnitRegistry
The new registry.
See Also
--------
set_application_registry
"""
if isinstance(new_registry, type(self)):
new_registry = new_registry.get()
if not isinstance(new_registry, (LazyRegistry, UnitRegistry)):
raise TypeError("Expected UnitRegistry; got %s" % type(new_registry))
logger.debug(
"Changing app registry from %r to %r.", self._registry, new_registry
)
self._registry = new_registry
def __getattr__(self, name):
return getattr(self._registry, name)
def __setattr__(self, name, value):
if name in self.__slots__:
super().__setattr__(name, value)
else:
setattr(self._registry, name, value)
def __dir__(self):
return dir(self._registry)
def __getitem__(self, item):
return self._registry[item]
def __call__(self, *args, **kwargs):
return self._registry(*args, **kwargs)
def __contains__(self, item):
return self._registry.__contains__(item)
def __iter__(self):
return iter(self._registry)