1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
|
"""
pint.facets.systems.registry
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
from numbers import Number
from typing import TYPE_CHECKING, Any, Generic
from ... import errors
from ...compat import TypeAlias
from ..plain import QuantityT, UnitT
if TYPE_CHECKING:
from ..._typing import Quantity, Unit
from ..._typing import UnitLike
from ...util import UnitsContainer as UnitsContainerT
from ...util import (
create_class_with_registry,
to_units_container,
)
from ..group import GenericGroupRegistry
from . import objects
from .definitions import SystemDefinition
class GenericSystemRegistry(
Generic[QuantityT, UnitT], GenericGroupRegistry[QuantityT, UnitT]
):
"""Handle of Systems.
Conversion between units with different dimensions according
to previously established relations (contexts).
(e.g. in the spectroscopy, conversion between frequency and energy is possible)
Capabilities:
- Register systems.
- List systems
- Get or get the default system.
- Parse @group directive.
"""
# TODO: Change this to System: System to specify class
# and use introspection to get system class as a way
# to enjoy typing goodies
System: type[objects.System]
def __init__(self, system: str | None = None, **kwargs):
super().__init__(**kwargs)
#: Map system name to system.
self._systems: dict[str, objects.System] = {}
#: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer)
self._base_units_cache: dict[UnitsContainerT, UnitsContainerT] = {}
self._default_system_name: str | None = system
def _init_dynamic_classes(self) -> None:
"""Generate subclasses on the fly and attach them to self"""
super()._init_dynamic_classes()
self.System = create_class_with_registry(self, objects.System)
def _after_init(self) -> None:
"""Invoked at the end of ``__init__``.
- Create default group and add all orphan units to it
- Set default system
"""
super()._after_init()
#: System name to be used by default.
self._default_system_name = self._default_system_name or self._defaults.get(
"system", None
)
def _register_definition_adders(self) -> None:
super()._register_definition_adders()
self._register_adder(SystemDefinition, self._add_system)
def _add_system(self, sd: SystemDefinition) -> None:
if sd.name in self._systems:
raise ValueError(f"System {sd.name} already present in registry")
try:
# As a System is a SharedRegistryObject
# it adds itself to the registry.
self.System.from_definition(sd)
except KeyError as e:
# TODO: fix this error message
raise errors.DefinitionError(f"unknown dimension {e} in context")
@property
def sys(self):
return objects.Lister(self._systems)
@property
def default_system(self) -> str | None:
return self._default_system_name
@default_system.setter
def default_system(self, name: str) -> None:
if name:
if name not in self._systems:
raise ValueError("Unknown system %s" % name)
self._base_units_cache = {}
self._default_system_name = name
def get_system(self, name: str, create_if_needed: bool = True) -> objects.System:
"""Return a Group.
Parameters
----------
name : str
Name of the group to be.
create_if_needed : bool
If True, create a group if not found. If False, raise an Exception.
(Default value = True)
Returns
-------
type
System
"""
if name in self._systems:
return self._systems[name]
if not create_if_needed:
raise ValueError("Unknown system %s" % name)
return self.System(name)
def get_base_units(
self,
input_units: UnitLike | Quantity,
check_nonmult: bool = True,
system: str | objects.System | None = None,
) -> tuple[Number, Unit]:
"""Convert unit or dict of units to the plain units.
If any unit is non multiplicative and check_converter is True,
then None is returned as the multiplicative factor.
Unlike PlainRegistry, in this registry root_units might be different
from base_units
Parameters
----------
input_units : UnitsContainer or str
units
check_nonmult : bool
if True, None will be returned as the
multiplicative factor if a non-multiplicative
units is found in the final Units. (Default value = True)
system :
(Default value = None)
Returns
-------
type
multiplicative factor, plain units
"""
input_units = to_units_container(input_units)
f, units = self._get_base_units(input_units, check_nonmult, system)
return f, self.Unit(units)
def _get_base_units(
self,
input_units: UnitsContainerT,
check_nonmult: bool = True,
system: str | objects.System | None = None,
):
if system is None:
system = self._default_system_name
# The cache is only done for check_nonmult=True and the current system.
if (
check_nonmult
and system == self._default_system_name
and input_units in self._base_units_cache
):
return self._base_units_cache[input_units]
factor, units = self.get_root_units(input_units, check_nonmult)
if not system:
return factor, units
# This will not be necessary after integration with the registry
# as it has a UnitsContainer intermediate
units = to_units_container(units, self)
destination_units = self.UnitsContainer()
bu = self.get_system(system, False).base_units
for unit, value in units.items():
if unit in bu:
new_unit = bu[unit]
new_unit = to_units_container(new_unit, self)
destination_units *= new_unit**value
else:
destination_units *= self.UnitsContainer({unit: value})
base_factor = self.convert(factor, units, destination_units)
if check_nonmult:
self._base_units_cache[input_units] = base_factor, destination_units
return base_factor, destination_units
def get_compatible_units(
self, input_units: UnitsContainerT, group_or_system: str | None = None
) -> frozenset[Unit]:
""" """
group_or_system = group_or_system or self._default_system_name
if group_or_system is None:
return super().get_compatible_units(input_units)
input_units = to_units_container(input_units)
equiv = self._get_compatible_units(input_units, group_or_system)
return frozenset(self.Unit(eq) for eq in equiv)
def _get_compatible_units(
self, input_units: UnitsContainerT, group_or_system: str | None = None
) -> frozenset[Unit]:
if group_or_system and group_or_system in self._systems:
members = self._systems[group_or_system].members
# group_or_system has been handled by System
return frozenset(members & super()._get_compatible_units(input_units))
try:
# This will be handled by groups
return super()._get_compatible_units(input_units, group_or_system)
except ValueError as ex:
# It might be also a system
if "Unknown Group" in str(ex):
raise ValueError(
"Unknown Group o System with name '%s'" % group_or_system
) from ex
raise ex
class SystemRegistry(
GenericSystemRegistry[objects.SystemQuantity[Any], objects.SystemUnit]
):
Quantity: TypeAlias = objects.SystemQuantity[Any]
Unit: TypeAlias = objects.SystemUnit
|