Source code for encomp.utypes

"""
Contains type definitions for :py:class:`encomp.units.Quantity` objects.

The dimensionalities defined in this module can be combined with ``*`` and ``/``.
Some commonly used derived dimensionalities (like density) are defined for convenience.
"""

from __future__ import annotations

from typing import Literal, TypeVar, cast, get_origin

import numpy as np
import polars as pl
from pint.util import UnitsContainer

BASE_SI_UNITS = (
    "m",
    "kg",
    "s",
    "K",
    "mol",
    "A",
    "cd",
)

# these string literals are used to infer the dimensionality of commonly created quantities
# they are only used by type checkers and ignored at runtime
DimensionlessUnits = Literal[
    "",
    "%",
    "percent",
    "pct",
    "-",
    "dimensionless",
]

CurrencyUnits = Literal[
    "SEK",
    "EUR",
    "USD",
    "kSEK",
    "kEUR",
    "kUSD",
    "MSEK",
    "MEUR",
    "MUSD",
]

CurrencyPerEnergyUnits = Literal[
    "SEK/MWh",
    "EUR/MWh",
    "SEK/kWh",
    "EUR/kWh",
    "SEK/GWh",
    "EUR/GWh",
    "SEK/TWh",
    "EUR/TWh",
]

CurrencyPerMassUnits = Literal[
    "SEK/kg",
    "EUR/kg",
    "SEK/t",
    "EUR/t",
    "SEK/ton",
    "EUR/ton",
    "SEK/g",
    "EUR/g",
    "SEK/mg",
    "EUR/mg",
    "SEK/ug",
    "EUR/ug",
]

CurrencyPerVolumeUnits = Literal[
    "SEK/L",
    "EUR/L",
    "SEK/l",
    "EUR/l",
    "SEK/liter",
    "EUR/liter",
    "SEK/m3",
    "EUR/m3",
    "SEK/m^3",
    "EUR/m^3",
    "SEK/m**3",
    "EUR/m**3",
    "SEK/m³",
    "EUR/m³",
]

CurrencyPerTimeUnits = Literal[
    "SEK/h",
    "EUR/h",
    "SEK/hr",
    "EUR/hr",
    "SEK/hour",
    "EUR/hour",
    "SEK/d",
    "EUR/d",
    "SEK/day",
    "EUR/day",
    "SEK/w",
    "EUR/w",
    "SEK/week",
    "EUR/week",
    "SEK/y",
    "EUR/y",
    "SEK/yr",
    "EUR/yr",
    "SEK/year",
    "EUR/year",
    "SEK/a",
    "EUR/a",
]


LengthUnits = Literal[
    "m",
    "meter",
    "km",
    "cm",
    "mm",
    "um",
]

MassUnits = Literal[
    "kg",
    "g",
    "ton",
    "tonne",
    "t",
    "mg",
    "ug",
]

TimeUnits = Literal[
    "s",
    "second",
    "min",
    "minute",
    "h",
    "hr",
    "hour",
    "d",
    "day",
    "w",
    "week",
    "y",
    "yr",
    "a",
    "year",
    "ms",
    "us",
]

TemperatureUnits = Literal[
    "degC",
    "°C",
    "K",
    "degF",
    "°F",
    "℃",
    "℉",
]

TemperatureDifferenceUnits = Literal[
    "delta_°C",
    "delta_degC",
    "Δ°C",
    "Δ℃",
    "delta_°F",
    "delta_degF",
    "Δ°F",
    "Δ℉",
]

SubstanceUnits = Literal[
    "mol",
    "kmol",
]

MolarMassUnits = Literal[
    "g/mol",
    "kg/kmol",
]

SubstancePerMassUnits = Literal[
    "mol/g",
    "kmol/kg",
]

CurrentUnits = Literal[
    "A",
    "mA",
]

LuminosityUnits = Literal["lm"]

AreaUnits = Literal[
    "m2",
    "m^2",
    "m**2",
    "m²",
    "cm2",
    "cm^2",
    "cm**2",
    "cm²",
]

VolumeUnits = Literal[
    "L",
    "l",
    "liter",
    "m3",
    "m^3",
    "m³",
    "m**3",
    "dm3",
    "dm^3",
    "dm³",
    "dm**3",
    "cm3",
    "cm^3",
    "cm³",
    "cm**3",
]

NormalVolumeUnits = Literal[
    "normal liter",
    "Nm3",
    "nm3",
    "Nm^3",
    "nm^3",
    "Nm³",
    "nm³",
    "Nm**3",
    "nm**3",
]

PressureUnits = Literal[
    "bar",
    "kPa",
    "Pa",
    "MPa",
    "mbar",
    "mmHg",
    "psi",
    "atm",
    "N/m2",
    "N/m^2",
    "N/m**2",
    "N/m²",
]

MassFlowUnits = Literal[
    "kg/s",
    "kg/h",
    "kg/hr",
    "g/s",
    "g/h",
    "g/hr",
    "ton/h",
    "t/h",
    "ton/hr",
    "t/hr",
    "t/d",
    "ton/day",
    "t/w",
    "ton/week",
    "t/y",
    "t/a",
    "t/year",
    "ton/y",
    "ton/a",
    "ton/year",
]

VolumeFlowUnits = Literal[
    "m3/s",
    "m3/h",
    "m3/hr",
    "m**3/s",
    "m**3/h",
    "m**3/hr",
    "m^3/s",
    "m^3/h",
    "m^3/hr",
    "m³/s",
    "m³/h",
    "m³/hr",
    "liter/second",
    "l/s",
    "L/s",
    "liter/s",
    "liter/hour",
    "l/h",
    "L/h",
    "L/hr",
    "l/hr",
]

NormalVolumeFlowUnits = Literal[
    "Nm3/s",
    "Nm3/h",
    "Nm3/hr",
    "nm3/s",
    "nm3/h",
    "nm3/hr",
    "Nm^3/s",
    "Nm^3/h",
    "Nm^3/hr",
    "nm^3/s",
    "nm^3/h",
    "nm^3/hr",
    "Nm³/s",
    "Nm³/h",
    "Nm³/hr",
    "nm³/s",
    "nm³/h",
    "nm³/hr",
    "Nm**3/s",
    "Nm**3/h",
    "Nm**3/hr",
    "nm**3/s",
    "nm**3/h",
    "nm**3/hr",
]

DensityUnits = Literal[
    "kg/m3",
    "kg/m**3",
    "kg/m^3",
    "kg/m³",
    "kg/liter",
    "g/l",
    "g/L",
    "gram/liter",
]

SpecificVolumeUnits = Literal[
    "m3/kg",
    "m^3/kg",
    "m³/kg",
    "l/g",
    "L/g",
]

NormalVolumePerMassUnits = Literal[
    "Nm3/kg",
    "Nm^3/kg",
    "Nm³/kg",
    "nm3/kg",
    "nm^3/kg",
    "nm³/kg",
]

EnergyUnits = Literal[
    "J",
    "kJ",
    "MJ",
    "GJ",
    "TJ",
    "PJ",
    "kWh",
    "MWh",
    "Wh",
    "GWh",
    "TWh",
]

PowerUnits = Literal[
    "W",
    "kW",
    "MW",
    "GW",
    "TW",
    "mW",
    "kWh/d",
    "kWh/w",
    "kWh/y",
    "kWh/yr",
    "kWh/year",
    "MWh/d",
    "MWh/w",
    "MWh/y",
    "MWh/yr",
    "MWh/year",
    "GWh/d",
    "GWh/w",
    "GWh/y",
    "GWh/yr",
    "GWh/year",
    "TWh/d",
    "TWh/w",
    "TWh/y",
    "TWh/yr",
    "TWh/year",
]

VelocityUnits = Literal[
    "m/s",
    "km/s",
    "m/min",
    "cm/s",
    "cm/min",
    "km/h",
    "kmh",
    "kph",
]

ForceUnits = Literal[
    "N",
    "kN",
    "mN",
]

DynamicViscosityUnits = Literal[
    "Pa*s",
    "Pa s",
    "cP",
]

KinematicViscosityUnits = Literal[
    "m2/s",
    "m**2/s",
    "m^2/s",
    "m²/s",
    "cSt",
    "cm2/s",
    "cm**2/s",
    "cm^2/s",
    "cm²/s",
]

EnergyPerMassUnits = Literal[
    "MJ/kg",
    "MWh/kg",
    "kJ/kg",
    "kWh/kg",
    "MJ/t",
    "MWh/t",
    "kJ/t",
    "kWh/t",
    "MJ/ton",
    "MWh/ton",
    "kJ/ton",
    "kWh/ton",
]

SpecificHeatCapacityUnits = Literal[
    "kJ/kg/K",
    "kJ/kg/delta_degC",
    "kJ/kg/Δ°C",
    "kJ/kg/Δ℃",
    "kJ/kg/°C",
    "kJ/kg/℃",
    "kJ/kg/degC",
    "J/kg/K",
    "J/kg/delta_degC",
    "J/kg/Δ°C",
    "J/kg/Δ℃",
    "J/kg/°C",
    "J/kg/℃",
    "J/kg/degC",
    "J/g/K",
    "J/g/delta_degC",
    "J/g/Δ°C",
    "J/g/Δ℃",
    "J/g/°C",
    "J/g/℃",
    "J/g/degC",
]

ThermalConductivityUnits = Literal[
    "W/m/K",
    "W/m/delta_degC",
    "W/m/Δ°C",
    "W/m/Δ℃",
    "kW/m/K",
    "mW/m/K",
]

HeatTransferCoefficientUnits = Literal[
    "W/m2/K",
    "W/m^2/K",
    "W/m**2/K",
    "W/m²/K",
    "W/m2/delta_degC",
    "W/m^2/delta_degC",
    "W/m**2/delta_degC",
    "W/m²/delta_degC",
    "W/m2/Δ°C",
    "W/m^2/Δ°C",
    "W/m**2/Δ°C",
    "W/m²/Δ°C",
    "kW/m2/K",
    "kW/m^2/K",
    "kW/m**2/K",
    "kW/m²/K",
]

AllUnits = (
    DimensionlessUnits
    | CurrencyUnits
    | CurrencyPerEnergyUnits
    | CurrencyPerMassUnits
    | CurrencyPerVolumeUnits
    | CurrencyPerTimeUnits
    | LengthUnits
    | MassUnits
    | TimeUnits
    | TemperatureUnits
    | TemperatureDifferenceUnits
    | SubstanceUnits
    | MolarMassUnits
    | SubstancePerMassUnits
    | CurrentUnits
    | LuminosityUnits
    | AreaUnits
    | VolumeUnits
    | NormalVolumeUnits
    | PressureUnits
    | MassFlowUnits
    | VolumeFlowUnits
    | NormalVolumeFlowUnits
    | DensityUnits
    | SpecificVolumeUnits
    | NormalVolumePerMassUnits
    | EnergyUnits
    | PowerUnits
    | VelocityUnits
    | ForceUnits
    | DynamicViscosityUnits
    | KinematicViscosityUnits
    | EnergyPerMassUnits
    | SpecificHeatCapacityUnits
    | ThermalConductivityUnits
    | HeatTransferCoefficientUnits
)


[docs] def get_registered_units() -> dict[str, tuple[str, ...]]: ret: dict[str, tuple[str, ...]] = {} for k, v in globals().items(): if get_origin(v) is Literal and k.endswith("Units"): ret[k.removesuffix("Units")] = v.__args__ return ret
class _DimensionalityMeta(type): def __eq__(cls, other: object) -> bool: if not isinstance(other, type): return False return cls.__qualname__ == other.__qualname__ def __hash__(cls) -> int: return id(cls)
[docs] class Dimensionality(metaclass=_DimensionalityMeta): r""" Represents the *dimensionality* of a unit, i.e. a combination (product) of the base dimensions (with optional rational exponents). A dimension ca be expressed as .. math:: \Pi \, d^n_d, d \in \{T, L, M ,I, \Theta, N, J, \ldots\}, n_d \in \mathbb{Q} where $\{T, L, M, ...\}$ are the base dimensions (time, length, mass, ...) and $n_d$ is a rational number. Subclasses of this abstract base class are used as type parameters when creating instances of :py:class:`encomp.units.Quantity`. The ``dimensions`` class attribute defines the dimensions of the dimensionality using an instance of ``pint.unit.UnitsContainer``. """ # set _distinct to False for dimensionalities that are not distinct # purely based on the dimensions _distinct: bool | None = None # set to True for intermediate subclasses of Dimensionality # these cannot be initialized directly, the must be subclassed further _intermediate: bool = False # sentinel object to indicate unset UnitsContainer _UnsetUC = UnitsContainer() dimensions: UnitsContainer = _UnsetUC # keeps track of all the dimensionalities that have been # used in the current process # use the class definition as key, since multiple dimensionalities # might have the same UnitsContainer _registry: dict[type[Dimensionality], UnitsContainer] = {} # also store a reversed map, this might not contain all items in _registry # dimensionalities where is_distinct() returns True will have precedence _registry_reversed: dict[UnitsContainer, type[Dimensionality]] = {} def __init_subclass__(cls) -> None: if cls._intermediate: return if cls.dimensions is cls._UnsetUC: raise TypeError( f"Cannot initialize {cls}, class attribute 'dimensionality' is not defined for this subclass" ) # ensure that the subclass names are unique if cls.__name__ in (subcls.__name__ for subcls in cls._registry): existing = next(filter(lambda x: x.__name__ == cls.__name__, cls._registry)) # compare string representations of the UnitsContainer instances # might run into issues with float accuracy otherwise # the UnitsContainer.__eq__ method checks hash(frozenset(self._d.items())) if str(cls.dimensions) != str(existing.dimensions): raise TypeError( "Cannot create dimensionality subclass with " f'name "{cls.__name__}", another subclass with ' "this name already exists and the dimensions do " f"not match: {cls.dimensions} != {existing.dimensions}" ) # don't create a new subclass with the same name return # make sure a subclass of an existing Dimensionality has the same dimensions # the first element in __mro__ is the class that is being created, the # second is the direct parent class # parent must be either Dimensionality or a subclass parent = cast(type[Dimensionality], cls.__mro__[1]) # ignore this check if the parent is the base class Dimensionality if parent.dimensions is not cls._UnsetUC and parent.dimensions != cls.dimensions: raise TypeError( f"Cannot create subclass of {parent} where " "the dimensions do not match. Tried to " f"create subclass with dimensions {cls.dimensions}, but " f"the parent has dimensions {parent.dimensions}" ) # this will never happen, # since the class name was already checked for duplicates if cls in cls._registry: return cls._registry[cls] = cls.dimensions # unless specifically overridden with _distinct, # this will be True only for the first subtype with specific dimensions if cls.is_distinct(): cls._registry_reversed[cls.dimensions] = cls
[docs] @classmethod def get_dimensionality(cls, dimensions: UnitsContainer) -> type[Dimensionality]: if dimensions in cls._registry_reversed: return cls._registry_reversed[dimensions] # create a new, custom Dimensionality # not possible to generate a proper name for this, # so it will just contain the literal dimensions # this will call __init_subclass__ to register the type _Dimensionality = cast( type[Dimensionality], type( f"Dimensionality[{dimensions}]", (Dimensionality,), {"dimensions": dimensions}, ), ) return _Dimensionality
[docs] @classmethod def is_distinct(cls) -> bool: if cls._distinct is None: # special case if dimensions was overridden to None if getattr(cls, "dimensions", None) is None: return True ucs = list(cls._registry.values()) # NOTE: the output of this classmethod # might change when the registry is updated return ucs.count(cls.dimensions) == 1 return cls._distinct
_DimensionlessUC = UnitsContainer({}) _CurrencyUC = UnitsContainer({"[currency]": 1}) _NormalUC = UnitsContainer({"[normal]": 1}) _LengthUC = UnitsContainer({"[length]": 1}) _MassUC = UnitsContainer({"[mass]": 1}) _TimeUC = UnitsContainer({"[time]": 1}) _TemperatureUC = UnitsContainer({"[temperature]": 1}) _SubstanceUC = UnitsContainer({"[substance]": 1}) _CurrentUC = UnitsContainer({"[current]": 1}) _LuminosityUC = UnitsContainer({"[luminosity]": 1}) Numpy1DArray = np.ndarray[tuple[int], np.dtype[np.float64]] Numpy1DBoolArray = np.ndarray[tuple[int], np.dtype[np.bool]] MT = TypeVar( "MT", float, Numpy1DArray, pl.Series, pl.Expr, default=Numpy1DArray, ) MT_ = TypeVar( "MT_", float, Numpy1DArray, pl.Series, pl.Expr, default=Numpy1DArray, )
[docs] class UnknownDimensionality(Dimensionality): _intermediate = True
# type variables that represent a certain dimensionality # the DT_ type variable is used to signify a different (possible identical) dimensionality than DT DT = TypeVar("DT", bound=Dimensionality, default=UnknownDimensionality) DT_ = TypeVar("DT_", bound=Dimensionality, default=UnknownDimensionality)
[docs] class Dimensionless(Dimensionality): dimensions = _DimensionlessUC
[docs] class Normal(Dimensionality): dimensions = _NormalUC
[docs] class Length(Dimensionality): dimensions = _LengthUC
[docs] class Mass(Dimensionality): dimensions = _MassUC
[docs] class Time(Dimensionality): dimensions = _TimeUC
[docs] class Temperature(Dimensionality): _distinct = True dimensions = _TemperatureUC
[docs] class TemperatureDifference(Dimensionality): dimensions = _TemperatureUC
[docs] class Substance(Dimensionality): dimensions = _SubstanceUC
[docs] class Current(Dimensionality): dimensions = _CurrentUC
[docs] class Luminosity(Dimensionality): dimensions = _LuminosityUC
# derived dimensionalities _AreaUC = _LengthUC**2 _VolumeUC = _LengthUC**3 _NormalVolumeUC = _VolumeUC * _NormalUC _PressureUC = _MassUC / _LengthUC / _TimeUC**2 _MassFlowUC = _MassUC / _TimeUC _VolumeFlowUC = _VolumeUC / _TimeUC _NormalVolumeFlowUC = _NormalVolumeUC / _TimeUC _DensityUC = _MassUC / _VolumeUC _SpecificVolumeUC = 1 / _DensityUC _EnergyUC = _MassUC * _LengthUC**2 / _TimeUC**2 _PowerUC = _EnergyUC / _TimeUC _VelocityUC = _LengthUC / _TimeUC _ForceUC = _MassUC * _LengthUC / _TimeUC**2 _DynamicViscosityUC = _MassUC / _LengthUC / _TimeUC _KinematicViscosityUC = _LengthUC**2 / _TimeUC _FrequencyUC = 1 / _TimeUC _MolarMassUC = _MassUC / _SubstanceUC _MolarDensityUC = _SubstanceUC / _VolumeUC _CurrencyPerEnergyUC = _CurrencyUC / _EnergyUC _CurrencyPerMassUC = _CurrencyUC / _MassUC _CurrencyPerVolumeUC = _CurrencyUC / _VolumeUC _CurrencyPerTimeUC = _CurrencyUC / _TimeUC _PowerPerLengthUC = _PowerUC / _LengthUC _PowerPerAreaUC = _PowerUC / _AreaUC _PowerPerVolumeUC = _PowerUC / _VolumeUC _PowerPerTemperatureC = _PowerUC / _TemperatureUC _ThermalConductivityUC = _PowerUC / _LengthUC / _TemperatureUC _HeatTransferCoefficientUC = _PowerUC / _AreaUC / _TemperatureUC _MassPerNormalVolumeUC = _MassUC / _NormalVolumeUC _MassPerEnergyUC = _MassUC / _EnergyUC _MolarSpecificEntropyUC = _EnergyUC / _TemperatureUC / _SubstanceUC
[docs] class Area(Dimensionality): dimensions = _AreaUC
[docs] class Volume(Dimensionality): dimensions = _VolumeUC
[docs] class NormalVolume(Dimensionality): dimensions = _NormalVolumeUC
[docs] class Pressure(Dimensionality): dimensions = _PressureUC
[docs] class MassFlow(Dimensionality): dimensions = _MassFlowUC
[docs] class VolumeFlow(Dimensionality): dimensions = _VolumeFlowUC
[docs] class NormalVolumeFlow(Dimensionality): dimensions = _NormalVolumeFlowUC
[docs] class Density(Dimensionality): dimensions = _DensityUC
[docs] class SpecificVolume(Dimensionality): dimensions = _SpecificVolumeUC
[docs] class Energy(Dimensionality): dimensions = _EnergyUC
[docs] class Power(Dimensionality): dimensions = _PowerUC
[docs] class Velocity(Dimensionality): dimensions = _VelocityUC
[docs] class Force(Dimensionality): dimensions = _ForceUC
[docs] class DynamicViscosity(Dimensionality): dimensions = _DynamicViscosityUC
[docs] class KinematicViscosity(Dimensionality): dimensions = _KinematicViscosityUC
[docs] class Frequency(Dimensionality): dimensions = _FrequencyUC
[docs] class MolarMass(Dimensionality): dimensions = _MolarMassUC
[docs] class SubstancePerMass(Dimensionality): dimensions = 1 / _MolarMassUC
[docs] class MolarDensity(Dimensionality): dimensions = _MolarDensityUC
[docs] class Currency(Dimensionality): dimensions = _CurrencyUC
[docs] class CurrencyPerEnergy(Dimensionality): dimensions = _CurrencyPerEnergyUC
[docs] class CurrencyPerMass(Dimensionality): dimensions = _CurrencyPerMassUC
[docs] class CurrencyPerVolume(Dimensionality): dimensions = _CurrencyPerVolumeUC
[docs] class CurrencyPerTime(Dimensionality): dimensions = _CurrencyPerTimeUC
[docs] class PowerPerLength(Dimensionality): dimensions = _PowerPerLengthUC
[docs] class PowerPerArea(Dimensionality): dimensions = _PowerPerAreaUC
[docs] class PowerPerVolume(Dimensionality): dimensions = _PowerPerVolumeUC
[docs] class PowerPerTemperature(Dimensionality): dimensions = _PowerPerTemperatureC
[docs] class ThermalConductivity(Dimensionality): dimensions = _ThermalConductivityUC
[docs] class HeatTransferCoefficient(Dimensionality): dimensions = _HeatTransferCoefficientUC
[docs] class MassPerNormalVolume(Dimensionality): dimensions = _MassPerNormalVolumeUC
[docs] class MassPerEnergy(Dimensionality): dimensions = _MassPerEnergyUC
[docs] class MolarSpecificEntropy(Dimensionality): dimensions = _MolarSpecificEntropyUC
# not called "SpecificNormalVolume" since that term is not commonly used # this name is more explicit
[docs] class NormalVolumePerMass(Dimensionality): dimensions = 1 / _MassPerNormalVolumeUC
[docs] class NormalTemperature(Dimensionality): dimensions = _TemperatureUC * _NormalUC
# these dimensionalities might have different names depending on the context # they are not included as inputs for the autogenerated type hint # (unless the _distinct class attribute is set to True)
[docs] class EnergyPerMass(Dimensionality): _distinct = True dimensions = _EnergyUC / _MassUC
[docs] class HeatingValue(Dimensionality): dimensions = _EnergyUC / _MassUC
[docs] class LowerHeatingValue(Dimensionality): dimensions = _EnergyUC / _MassUC
[docs] class HigherHeatingValue(Dimensionality): dimensions = _EnergyUC / _MassUC
[docs] class SpecificEnthalpy(Dimensionality): dimensions = _EnergyUC / _MassUC
[docs] class SpecificInternalEnergy(Dimensionality): dimensions = _EnergyUC / _MassUC
[docs] class MolarSpecificEnthalpy(Dimensionality): _distinct = True dimensions = _EnergyUC / _SubstanceUC
[docs] class MolarSpecificInternalEnergy(Dimensionality): dimensions = _EnergyUC / _SubstanceUC
[docs] class SpecificHeatCapacity(Dimensionality): _distinct = True dimensions = _EnergyUC / _MassUC / _TemperatureUC
[docs] class SpecificEntropy(Dimensionality): dimensions = _EnergyUC / _MassUC / _TemperatureUC
# related to CoolProp humid air # these dimensionalities are not distinct, the same # combination of dimensions can be mean multiple things
[docs] class IndistinctDimensionality(Dimensionality): _intermediate = True _distinct = False
[docs] class SpecificHeatPerDryAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC / _TemperatureUC
[docs] class SpecificHeatPerHumidAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC / _TemperatureUC
[docs] class MixtureEnthalpyPerDryAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC
[docs] class MixtureEnthalpyPerHumidAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC
[docs] class MixtureEntropyPerDryAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC / _TemperatureUC
[docs] class MixtureEntropyPerHumidAir(IndistinctDimensionality): dimensions = _EnergyUC / _MassUC / _TemperatureUC
[docs] class MixtureVolumePerDryAir(IndistinctDimensionality): dimensions = _VolumeUC / _MassUC
[docs] class MixtureVolumePerHumidAir(IndistinctDimensionality): dimensions = _VolumeUC / _MassUC