Skip to content

Module unitexpr.unit

Module providing the classes:

  • UnitMeta
  • UnitBase
View Source
"""

Module providing the classes:

* UnitMeta

* UnitBase

"""

from __future__ import annotations

from typing import Any, Iterable, NamedTuple, Tuple

from lockattrs import protect

from ._unitexpr import UnitExprBase, UnitExprMeta, UnitExprMixin

from .unit_symbol import UnitSymbol

class UnitMeta(type):

    """

    Meta class of objects representing physical units.

    """

    def __new__(

        cls, cls_name, bases, attrs, unit_symbols: Iterable[UnitSymbol]

    ):

        attrs["__slots__"] = tuple()

        return super().__new__(

            cls,

            cls_name,

            (UnitExprMixin, *bases),

            attrs,

        )

    def __init__(

        self, cls_name, bases, attrs, unit_symbols: Iterable[UnitSymbol]

    ):

        # Define the unit expression type.

        expr_type = UnitExprMeta(

            cls_name=cls_name + "Expr",

            bases=(UnitExprMixin, UnitExprBase),

            attrs={"__slots__": tuple()},

            unit_symbols=unit_symbols,

            unit_type=self,

        )

        self.expr_type = expr_type

        # Define base units.

        base_dimensions = len(unit_symbols)

        zero = [0.0] * base_dimensions

        base_units = []

        for index, unit_symbol in enumerate(unit_symbols):

            vector = zero.copy()

            vector[index] = 1.0

            base_unit = self(

                unit_symbol.symbol,

                unit_symbol.name,

                unit_symbol.quantity,

                self.expr_type(

                    terms=(unit_symbol.symbol,),

                    exponents=(1.0,),

                    factor=1.0,

                    base_exponents=tuple(vector),

                    base_factor=1.0,

                ),

            )

            base_units.append(base_unit)

            setattr(self, unit_symbol.symbol, base_unit)

        self.base_units = tuple(base_units)

        self.base_exponents_zero = tuple(zero)

        self.valid_types = (self, self.expr_type)

        self.info_type = NamedTuple(

            "UnitInfo",

            symbol=str,

            name=str,

            quantity=str,

            terms=Tuple[UnitMeta],

            exponents=Tuple[float],

            factor=float,

            base_exponents=Tuple[float],

            base_factor=float,

            sub_terms=Tuple[str],

            sub_exponents=Tuple[str],

            sub_factor=float,

        )

        super().__init__(cls_name, bases, attrs)

    def is_base_unit(self, unit):

        """

        Returns `True` if `unit` is a base unit and `False` otherwise.

        """

        return unit in self.base_units

    # The decorator protects all attributes from modification

    # (after they have been initially set).

    # Any attempt at modification will raise an error of type

    # ProtectedAttributeError.

    @protect()

    def __setattr__(self, name: str, value: Any) -> None:

        return super().__setattr__(name, value)

class UnitBase(

    NamedTuple(

        "_UnitBase",

        symbol=str,

        name=str,

        quantity=str,

        base_exponents=Tuple[float],

        base_factor=float,

        sub_terms=Tuple[UnitMeta],

        sub_exponents=Tuple[float],

        sub_factor=float,

    )

):

    """Base class of objects representing physical units.

    To generate unit systems subclass `UnitBase` providing the base unit

    symbols. The base units will be available as class attributes (see

    example below).

    ``` python

    # Defining unit symbols

    unit_symbols = (

        UnitSymbol(symbol='m','name'='meter',quantity='length'),

        UnitSymbol(symbol='s','name'='second',quantity='time'),

    )

    # Sub-classing the base class `UnitBase`

    class MetricUnit(UnitBase, metaclass=UnitMeta,

          unit_symbols=unit_symbols):

        pass

    # Base units are available as class attributes.

    m = MetricUnit.m

    s = MetricUnit.s

    # Declaring derived units

    c = MetricUnit('c', 'speed of light', 'velocity', expr=299792458*m/s)

    ```

    """

    __slots__ = ()

    def __new__(

        cls, symbol: str, name: str, quantity: str, expr: UnitExprBase

    ):

        if cls == UnitBase:

            raise TypeError(f"Class {cls} must be subclassed.")

        if not isinstance(expr, cls.expr_type):

            raise TypeError(

                f"Expected expression of type {cls.expr_type}. "

                + f"Found {type(expr)}."

            )

        return super().__new__(

            cls,

            symbol,

            name,

            quantity,

            base_exponents=expr.base_exponents,

            base_factor=expr.base_factor,

            sub_terms=expr.terms,

            sub_exponents=expr.exponents,

            sub_factor=expr.factor,

        )

    def __eq__(self, other: object) -> bool:

        """

        Returns `True` if:

        * the unit `self` and the unit/unit-expression `other` match when

        resolved in terms of base units.

        * other is numeric and the resolved expression of `self` represents a

        the same number. (All base exponents must be zero).

        """

        if isinstance(other, self.expr_type):

            return (

                self.base_factor == other.base_factor

                and self.base_exponents == other.base_exponents

            )

        if isinstance(other, (int, float)):

            return (

                self.base_exponents == self.base_exponents_zero

                and self.base_factor == other

            )

        return self is other

    def __ne__(self, other: object) -> bool:

        """

        Returns `True` if:

        * the unit `self` and the unit/unit-expression `other`

          do not match when resolved in terms of base units.

        * other is numeric and the resolved expression of `self` represents a

        different number. (All base exponents must be zero).

        """

        if isinstance(other, self.expr_type):

            return (

                self.base_factor != other.base_factor

                or self.base_exponents != other.base_exponents

            )

        if isinstance(other, (int, float)):

            return (

                self.base_exponents != self.base_exponents_zero

                or self.base_factor != other

            )

        return self is not other

    def __hash__(self) -> int:

        return id(self)

    def __repr__(self):

        """

        Returns a string representation of the unit object.

        """

        return self.symbol

    def __str__(self):

        """

        Returns a string representation of the unit object.

        """

        return self.symbol

    @property

    def info(self) -> NamedTuple:

        """

        Returns a `NamedTuple` containing detailed object information.

        """

        return self.info_type(

            self.symbol,

            self.name,

            self.quantity,

            (self,),

            self.exponents,

            self.factor,

            self.base_exponents,

            self.base_factor,

            self.sub_terms,

            self.sub_exponents,

            self.sub_factor,

        )

    @property

    def expr(self) -> UnitExprBase:

        """

        Returns an expression representing `self` in terms of

        `sub_terms`.

        ``` python

        J = SiUnit('J', 'Joule', 'energy', expr=N*m)

        print(J.expr)  # prints: N*m

        ```

        """

        return self.expr_type(

            terms=self.sub_terms,

            exponents=self.sub_exponents,

            factor=self.sub_factor,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    @property

    def self_expr(self) -> UnitExprBase:

        """

        Returns an expression representing `self` in terms of `self`.

        An alternative method of converting a unit to a unit expression is

        multiplication by 1.0.

        ``` python

        J = SiUnit('J', 'Joule', 'energy', expr=N*m)

        assert J.self_expr == J

        assert J.self_expr == 1.0*J

        assert type(J.self_expr) == SiUnit.expr_type



        print(J_expr) # Prints: J

        ```

        """

        return self.expr_type(

            terms=(self,),

            exponents=(1.0,),

            factor=1.0,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    @property

    def factor(self) -> float:

        """

        Returns the (scaling) `factor` of the unit expression.

        Always returns 1.0.

        """

        return 1.0

    @property

    def terms(self) -> Tuple[str]:

        """

        Returns the `terms` of the unit expression.

        """

        return (self,)

    @property

    def exponents(self) -> Tuple[float]:

        """

        Returns the exponents of the unit expression.

        """

        return (1.0,)

    @property

    def base_expr(self) -> UnitExprBase:

        """

        Returns an expression representing self in terms of base units.

        """

        dexpr = {}

        for term, exponent in zip(self.base_units, self.base_exponents):

            if exponent == 0.0:

                continue

            dexpr[term] = exponent

        return self.expr_type(

            tuple(dexpr.keys()),

            tuple(dexpr.values()),

            factor=self.base_factor,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    # ---------

    # Operators

    # ---------

Classes

UnitBase

class UnitBase(
    /,
    *args,
    **kwargs
)
View Source
class UnitBase(

    NamedTuple(

        "_UnitBase",

        symbol=str,

        name=str,

        quantity=str,

        base_exponents=Tuple[float],

        base_factor=float,

        sub_terms=Tuple[UnitMeta],

        sub_exponents=Tuple[float],

        sub_factor=float,

    )

):

    """Base class of objects representing physical units.

    To generate unit systems subclass `UnitBase` providing the base unit

    symbols. The base units will be available as class attributes (see

    example below).

    ``` python

    # Defining unit symbols

    unit_symbols = (

        UnitSymbol(symbol='m','name'='meter',quantity='length'),

        UnitSymbol(symbol='s','name'='second',quantity='time'),

    )

    # Sub-classing the base class `UnitBase`

    class MetricUnit(UnitBase, metaclass=UnitMeta,

          unit_symbols=unit_symbols):

        pass

    # Base units are available as class attributes.

    m = MetricUnit.m

    s = MetricUnit.s

    # Declaring derived units

    c = MetricUnit('c', 'speed of light', 'velocity', expr=299792458*m/s)

    ```

    """

    __slots__ = ()

    def __new__(

        cls, symbol: str, name: str, quantity: str, expr: UnitExprBase

    ):

        if cls == UnitBase:

            raise TypeError(f"Class {cls} must be subclassed.")

        if not isinstance(expr, cls.expr_type):

            raise TypeError(

                f"Expected expression of type {cls.expr_type}. "

                + f"Found {type(expr)}."

            )

        return super().__new__(

            cls,

            symbol,

            name,

            quantity,

            base_exponents=expr.base_exponents,

            base_factor=expr.base_factor,

            sub_terms=expr.terms,

            sub_exponents=expr.exponents,

            sub_factor=expr.factor,

        )

    def __eq__(self, other: object) -> bool:

        """

        Returns `True` if:

        * the unit `self` and the unit/unit-expression `other` match when

        resolved in terms of base units.

        * other is numeric and the resolved expression of `self` represents a

        the same number. (All base exponents must be zero).

        """

        if isinstance(other, self.expr_type):

            return (

                self.base_factor == other.base_factor

                and self.base_exponents == other.base_exponents

            )

        if isinstance(other, (int, float)):

            return (

                self.base_exponents == self.base_exponents_zero

                and self.base_factor == other

            )

        return self is other

    def __ne__(self, other: object) -> bool:

        """

        Returns `True` if:

        * the unit `self` and the unit/unit-expression `other`

          do not match when resolved in terms of base units.

        * other is numeric and the resolved expression of `self` represents a

        different number. (All base exponents must be zero).

        """

        if isinstance(other, self.expr_type):

            return (

                self.base_factor != other.base_factor

                or self.base_exponents != other.base_exponents

            )

        if isinstance(other, (int, float)):

            return (

                self.base_exponents != self.base_exponents_zero

                or self.base_factor != other

            )

        return self is not other

    def __hash__(self) -> int:

        return id(self)

    def __repr__(self):

        """

        Returns a string representation of the unit object.

        """

        return self.symbol

    def __str__(self):

        """

        Returns a string representation of the unit object.

        """

        return self.symbol

    @property

    def info(self) -> NamedTuple:

        """

        Returns a `NamedTuple` containing detailed object information.

        """

        return self.info_type(

            self.symbol,

            self.name,

            self.quantity,

            (self,),

            self.exponents,

            self.factor,

            self.base_exponents,

            self.base_factor,

            self.sub_terms,

            self.sub_exponents,

            self.sub_factor,

        )

    @property

    def expr(self) -> UnitExprBase:

        """

        Returns an expression representing `self` in terms of

        `sub_terms`.

        ``` python

        J = SiUnit('J', 'Joule', 'energy', expr=N*m)

        print(J.expr)  # prints: N*m

        ```

        """

        return self.expr_type(

            terms=self.sub_terms,

            exponents=self.sub_exponents,

            factor=self.sub_factor,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    @property

    def self_expr(self) -> UnitExprBase:

        """

        Returns an expression representing `self` in terms of `self`.

        An alternative method of converting a unit to a unit expression is

        multiplication by 1.0.

        ``` python

        J = SiUnit('J', 'Joule', 'energy', expr=N*m)

        assert J.self_expr == J

        assert J.self_expr == 1.0*J

        assert type(J.self_expr) == SiUnit.expr_type



        print(J_expr) # Prints: J

        ```

        """

        return self.expr_type(

            terms=(self,),

            exponents=(1.0,),

            factor=1.0,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    @property

    def factor(self) -> float:

        """

        Returns the (scaling) `factor` of the unit expression.

        Always returns 1.0.

        """

        return 1.0

    @property

    def terms(self) -> Tuple[str]:

        """

        Returns the `terms` of the unit expression.

        """

        return (self,)

    @property

    def exponents(self) -> Tuple[float]:

        """

        Returns the exponents of the unit expression.

        """

        return (1.0,)

    @property

    def base_expr(self) -> UnitExprBase:

        """

        Returns an expression representing self in terms of base units.

        """

        dexpr = {}

        for term, exponent in zip(self.base_units, self.base_exponents):

            if exponent == 0.0:

                continue

            dexpr[term] = exponent

        return self.expr_type(

            tuple(dexpr.keys()),

            tuple(dexpr.values()),

            factor=self.base_factor,

            base_exponents=self.base_exponents,

            base_factor=self.base_factor,

        )

    # ---------

    # Operators

    # ---------

Ancestors (in MRO)

  • unitexpr.unit._UnitBase
  • builtins.tuple

Descendants

  • unitexpr.sc_units.ScUnit
  • unitexpr.si_units.SiUnit

Class variables

base_exponents
base_factor
name
quantity
sub_exponents
sub_factor
sub_terms
symbol

Instance variables

base_expr

Returns an expression representing self in terms of base units.

exponents

Returns the exponents of the unit expression.

expr

Returns an expression representing self in terms of

sub_terms.

J = SiUnit('J', 'Joule', 'energy', expr=N*m)
print(J.expr)  # prints: N*m

factor

Returns the (scaling) factor of the unit expression.

Always returns 1.0.

info

Returns a NamedTuple containing detailed object information.

self_expr

Returns an expression representing self in terms of self.

An alternative method of converting a unit to a unit expression is multiplication by 1.0.

J = SiUnit('J', 'Joule', 'energy', expr=N*m)
assert J.self_expr == J
assert J.self_expr == 1.0*J
assert type(J.self_expr) == SiUnit.expr_type

print(J_expr) # Prints: J

terms

Returns the terms of the unit expression.

Methods

count

def count(
    self,
    value,
    /
)

Return number of occurrences of value.

index

def index(
    self,
    value,
    start=0,
    stop=9223372036854775807,
    /
)

Return first index of value.

Raises ValueError if the value is not present.

UnitMeta

class UnitMeta(
    cls_name,
    bases,
    attrs,
    unit_symbols: 'Iterable[UnitSymbol]'
)
View Source
class UnitMeta(type):

    """

    Meta class of objects representing physical units.

    """

    def __new__(

        cls, cls_name, bases, attrs, unit_symbols: Iterable[UnitSymbol]

    ):

        attrs["__slots__"] = tuple()

        return super().__new__(

            cls,

            cls_name,

            (UnitExprMixin, *bases),

            attrs,

        )

    def __init__(

        self, cls_name, bases, attrs, unit_symbols: Iterable[UnitSymbol]

    ):

        # Define the unit expression type.

        expr_type = UnitExprMeta(

            cls_name=cls_name + "Expr",

            bases=(UnitExprMixin, UnitExprBase),

            attrs={"__slots__": tuple()},

            unit_symbols=unit_symbols,

            unit_type=self,

        )

        self.expr_type = expr_type

        # Define base units.

        base_dimensions = len(unit_symbols)

        zero = [0.0] * base_dimensions

        base_units = []

        for index, unit_symbol in enumerate(unit_symbols):

            vector = zero.copy()

            vector[index] = 1.0

            base_unit = self(

                unit_symbol.symbol,

                unit_symbol.name,

                unit_symbol.quantity,

                self.expr_type(

                    terms=(unit_symbol.symbol,),

                    exponents=(1.0,),

                    factor=1.0,

                    base_exponents=tuple(vector),

                    base_factor=1.0,

                ),

            )

            base_units.append(base_unit)

            setattr(self, unit_symbol.symbol, base_unit)

        self.base_units = tuple(base_units)

        self.base_exponents_zero = tuple(zero)

        self.valid_types = (self, self.expr_type)

        self.info_type = NamedTuple(

            "UnitInfo",

            symbol=str,

            name=str,

            quantity=str,

            terms=Tuple[UnitMeta],

            exponents=Tuple[float],

            factor=float,

            base_exponents=Tuple[float],

            base_factor=float,

            sub_terms=Tuple[str],

            sub_exponents=Tuple[str],

            sub_factor=float,

        )

        super().__init__(cls_name, bases, attrs)

    def is_base_unit(self, unit):

        """

        Returns `True` if `unit` is a base unit and `False` otherwise.

        """

        return unit in self.base_units

    # The decorator protects all attributes from modification

    # (after they have been initially set).

    # Any attempt at modification will raise an error of type

    # ProtectedAttributeError.

    @protect()

    def __setattr__(self, name: str, value: Any) -> None:

        return super().__setattr__(name, value)

Ancestors (in MRO)

  • builtins.type

Methods

is_base_unit

def is_base_unit(
    self,
    unit
)

Returns True if unit is a base unit and False otherwise.

View Source
    def is_base_unit(self, unit):

        """

        Returns `True` if `unit` is a base unit and `False` otherwise.

        """

        return unit in self.base_units

mro

def mro(
    self,
    /
)

Return a type's method resolution order.