Skip to content

ucon.system

System-level value types: BaseUnits (the renamed v1.7 UnitSystem), the new UnitSystem value type, the per-instance AlgebraCache, and the use / active activation surface.

ucon.system

System-level value types for ucon.

This subpackage hosts the abstractions that record which units, dimensions, conversions, and basis transforms a coherent unit system uses.

Public surface (v1.8):

  • :class:BaseUnits -- a small named Mapping[Dimension, Unit] that records the canonical base unit per dimension. The renamed predecessor of the v1.7 top-level UnitSystem class.
  • :class:UnitSystem -- the new value type that owns a BaseUnits plus the registries (units, dimensions, conversions, basis_graph, contexts, constants) and a per-instance :class:AlgebraCache.
  • :class:AlgebraCache -- per-instance cache for Dimension algebra (mul/div/pow).
  • :func:use -- contextmanager that sets the active UnitSystem.
  • :func:active -- returns the active UnitSystem (snapshotting from globals if none has been set).

The PEP-562 alias from ucon import UnitSystem continues to resolve to :class:BaseUnits in v1.8 with a PendingDeprecationWarning. The new value type is reachable only via from ucon.system import UnitSystem.

AlgebraCache dataclass

Per-instance cache for Dimension algebraic operations.

Holds three sub-caches keyed by argument tuples:

  • mul: (Dimension, Dimension) -> Dimension
  • div: (Dimension, Dimension) -> Dimension
  • pow: (Dimension, exponent) -> Dimension

Owned by each :class:~ucon.system.UnitSystem instance.

clear()

Empty all three sub-caches.

ConflictPolicy

Bases: Enum

Policy for resolving name collisions when combining two :class:UnitSystem values.

  • :attr:RAISE (default): raise :class:ExtendConflict on any collision.
  • :attr:PREFER_SELF: keep the LHS definition; ignore the RHS.
  • :attr:PREFER_OTHER: replace the LHS definition with the RHS.

ExtendConflict

Bases: ValueError

Raised when :meth:UnitSystem.extend finds a name collision under :attr:ConflictPolicy.RAISE policy.

The registry attribute names the registry in which the conflict occurred ("units", "dimensions", "base_units", "conversions", "contexts", or "constants"); key is the colliding name.

InvalidRename

Bases: ValueError

Raised when :class:Bridge is constructed with a rename pair that is not synonym-equivalent.

A pair (src_name, dst_name) is synonym-equivalent only when src.units[src_name] and dst.units[dst_name] represent the same physical unit -- same dimension (under basis_transform when one is supplied) and identical base form. Definitional differences must be expressed via basis_transform or a custom conversion edge, not via rename.

Attributes

src_name : str The source-side rename key. dst_name : str The destination-side rename value. reason : str Why the pair is not synonym-equivalent.

RegistryDiff dataclass

Per-registry diff payload for :class:SystemDiff.

Attributes

added : frozenset[str] Keys present in other but not in self. removed : frozenset[str] Keys present in self but not in other. redefined : frozenset[str] Keys present in both with non-equal values.

SystemDiff dataclass

Structured difference between two :class:UnitSystem values.

Returned by :meth:UnitSystem.diff. Each field is a :class:RegistryDiff describing additions, removals, and redefinitions in that registry as self -> other.

BaseUnits dataclass

A named mapping from dimensions to base units.

Represents a coherent unit system like SI or Imperial, where each covered dimension has exactly one base unit. Partial systems are allowed (Imperial doesn't need mole).

Parameters

name : str The name of the unit system (e.g., "SI", "Imperial"). bases : dict[Dimension, Unit] Mapping from dimensions to their base units.

Raises

ValueError If name is empty, bases is empty, or a unit's dimension doesn't match its declared dimension key.

Examples

si = BaseUnits( ... name="SI", ... bases={ ... LENGTH: meter, ... MASS: kilogram, ... TIME: second, ... } ... ) si.base_for(LENGTH)

dimensions property

Return the set of dimensions covered by this system.

base_for(dim)

Return the base unit for a dimension.

Raises

DimensionNotCovered If this system has no base unit for the dimension.

covers(dim)

Return True if this system has a base unit for the dimension.

UnitSystem dataclass

A complete unit system as a value type.

Owns the basis, the registries (units, dimensions, conversion_graph, basis_graph, contexts, constants), the canonical base_units mapping, and a per-instance :class:AlgebraCache.

Parameters

basis : Basis Dimensional coordinate system. units : Mapping[str, Unit] Name registry of units. dimensions : Mapping[str, Dimension] Name registry of dimensions. base_units : BaseUnits Canonical base unit per covered dimension. conversion_graph : ConversionGraph Graph of unit conversion morphisms. basis_graph : BasisGraph Graph of basis transforms. contexts : Mapping[str, ConversionContext] Named cross-dimensional context bundles. Defaults to empty. constants : Mapping[str, Constant] Physical constants registry. Defaults to empty. _algebra_cache : AlgebraCache Per-instance dimension-algebra cache. Constructed empty by default.

Notes

The frozen dataclass holds references to mutable registries (dicts and graph objects). "Frozen" applies to the field bindings, not the contents of the registries -- mirroring how :class:BaseUnits already treats its bases dict. Equality and hash use identity for the mutable mappings/graphs and value equality for basis and base_units; the per-instance _algebra_cache is excluded from both.

resolve_unit(name)

Resolve a unit name/alias/expression string to a Unit or UnitProduct.

Delegates to :func:~ucon.resolver.parse_unit with system=self so the resolver draws from this system's unit registry.

Parameters

name : str Unit name, alias, scale-prefixed name, or composite expression (e.g., "ft", "km", "kg*m/s^2").

Returns

Unit or UnitProduct The resolved unit.

Raises

UnknownUnitError If the string cannot be resolved.

extend(other, *, on_conflict=ConflictPolicy.RAISE)

Return a new system combining self and other.

Unions each registry (units, dimensions, base_units, conversion edges, contexts, constants). Name collisions are resolved by on_conflict (a :class:ConflictPolicy member):

  • :attr:ConflictPolicy.RAISE (default): raise :class:ExtendConflict if any registry has two non-equal definitions for the same name. Identical values do not trigger the error.
  • :attr:ConflictPolicy.PREFER_SELF: keep the LHS value on conflict; the RHS value is silently discarded.
  • :attr:ConflictPolicy.PREFER_OTHER: replace the LHS value with the RHS value.

self.basis and self.basis_graph are preserved unchanged. Use :meth:with_basis / :meth:with_basis_graph to change them explicitly.

extend_many(*others, on_conflict=ConflictPolicy.RAISE)

Return a new system combining self with all others.

Semantically equivalent to chaining .extend() calls but performs a single conversion-graph copy regardless of how many systems are merged. This avoids the O(N * graph_size) cost of sequential extend() when composing many packages.

Parameters

*others : UnitSystem One or more systems to merge into self. on_conflict : ConflictPolicy Collision resolution strategy (same semantics as :meth:extend).

restrict(*, dimensions=None, units=None)

Return a new system retaining only the named dimensions and units.

Either filter may be None to leave that registry unrestricted. Filters compose: a unit survives only if both filters admit it.

The conversion graph is filtered to edges whose endpoints survive. base_units is filtered to entries whose dimension and unit survive. basis, basis_graph, contexts, and constants are preserved unchanged.

merge(other, resolver)

Return a new system combining self and other with a callable resolver for unit-name conflicts.

For every unit name shared between self.units and other.units whose values are non-equal, the resolver is invoked as resolver(self_unit, other_unit) and must return one of the two units (or a structurally-equal alternative). All other registries (dimensions, base_units, conversion edges, contexts, constants) use :attr:ConflictPolicy.PREFER_SELF semantics on conflict.

The resolver is not called for keys present in only one side or for keys whose values are already equal.

with_unit(unit)

Return a new system with unit added to the units registry.

If a unit with the same name already exists and is structurally equal, returns self unchanged. Otherwise raises :class:ExtendConflict on a non-equal collision.

with_conversion(edge)

Return a new system with edge added to the conversion graph.

Accepts the existing :class:ucon.contexts.ContextEdge value type (src, dst, map). Edges are bidirectional; the inverse is registered automatically by :meth:Graph.add_edge. Re-adding an identical edge is a no-op.

with_basis(basis)

Return a new system whose basis is replaced.

basis_graph is preserved as-is; callers are responsible for ensuring it contains the new basis (otherwise cross-basis operations from this system will fail at use time, not at construction).

with_basis_graph(graph)

Return a new system whose basis_graph is replaced.

subsystem_of(other)

Return True iff every unit, dimension, base-unit mapping, and conversion edge in self exists with the same definition in other.

basis and basis_graph are not compared; subsystem is a registry-content relation.

compatible_with(other)

Return True iff self and other agree wherever they overlap.

Every shared unit name maps to equal :class:Unit definitions; every shared dimension name maps to equal :class:Dimension definitions; every shared base-unit dimension maps to equal :class:Unit; every shared conversion edge (by endpoint-name) maps to the same :class:Map. Non-overlapping registry entries are ignored.

diff(other)

Return a :class:SystemDiff describing self -> other.

Each registry contributes a :class:RegistryDiff with added (in other but not self), removed (in self but not other), and redefined (in both with non-equal values).

shared_units(other)

Return the set of unit names defined in both systems.

shared_dimensions(other)

Return the set of dimension names defined in both systems.

adopt(n)

Rebind n to use this system's :class:Unit objects.

Walks n.unit (a :class:Unit or :class:UnitProduct) and checks that every component-unit name resolves in self.units. Returns a new :class:Number whose unit references point at the objects owned by this system; the numeric quantity and uncertainty are unchanged.

adopt performs no conversion. It is the trivial value- movement primitive for the case where unit names already match between systems. When names diverge or numeric correction is needed, use :class:Bridge.

Parameters

n : Number The value to re-bind.

Returns

Number A :class:Number whose unit references this system's :class:Unit objects.

Raises

UnknownUnitError If any component unit name is not defined in self.units.

ActiveContext dataclass

The ambient state bundle consulted by ucon's runtime.

A single :class:~contextvars.ContextVar (ucon._active) carries one :class:ActiveContext at a time. The payload bundles the :class:UnitSystem (units, dimensions, graphs) with the :class:~ucon.formulas.FormulaRegistry (kind-aware arithmetic dispatch table), the :class:~ucon.kinds.KindLattice (kind taxonomy consulted by Number.bound_to etc.), and strict (source-unit resolution mode for :meth:Number.to and arithmetic).

Keeping formulas and kinds off :class:UnitSystem is deliberate: a system does not own its formulas, and an :class:ActiveContext lets the caller swap the dispatch table without rebuilding the system.

Attributes

system : UnitSystem The active unit system. formulas : FormulaRegistry Kind-aware arithmetic dispatch table. kinds : KindLattice Kind taxonomy. strict : bool Whether source-unit resolution is identity-based (True, v2.0 default) or falls back to name-based lookup (False, v1.x ergonomics).

Bridge dataclass

Sanctioned cross-system value movement.

A Bridge carries a :class:~ucon.core.Number from src to dst, applying optional unit-name renames (synonym-equivalent pairs only) and an optional :class:~ucon.basis.BasisTransform for cross-basis rebasing.

Rename is synonym-only: each (a, b) in rename must satisfy src.units[a] and dst.units[b] representing the same physical unit. Definitional remapping (two systems naming distinct units identically) must be expressed via basis_transform or a custom conversion edge; it is rejected at construction with :class:InvalidRename.

Apply order is rename → basis_transform → identity-bind to dst :class:Unit. Under the synonym constraint the two layers commute; the order is pinned for trace-readability.

Attributes

src : UnitSystem The source system. dst : UnitSystem The destination system. rename : Mapping[str, str] Synonym-equivalent unit-name pairs. basis_transform : BasisTransform, optional Optional coordinate change for cross-basis values.

apply(n)

Move n from :attr:src to :attr:dst.

Order: rename (name layer) → basis_transform (numeric layer; no-op under the synonym constraint) → identity-bind to :attr:dst's :class:Unit objects. The result is a new :class:Number whose units reference :attr:dst's registry.

Raises

UnknownUnitError If a component-unit name (after rename) is not in self.dst.units.

inverse()

Return the reverse bridge dst → src.

Renames are reversed; basis_transform is inverted; the result is re-validated by :meth:__post_init__.

__matmul__(other)

Compose self @ other: apply other first, then self.

Requires other.dst is self.src (registry identity). The composed rename chains source names through other to self; the composed basis_transform is self.basis_transform @ other.basis_transform when both are present.

The resulting :class:Bridge is re-validated by :meth:__post_init__ so that pairs valid individually but not jointly are caught.

active()

Return the currently active :class:ActiveContext.

After import ucon, the active context is always set via eager initialization.

.. versionchanged:: 2.0 Returns :class:ActiveContext instead of :class:UnitSystem. Use :func:active_system for the prior semantics.

Raises

RuntimeError If called before import ucon has completed (i.e. the eager init block in ucon/__init__.py has not yet run).

active_system()

Return the active :class:UnitSystem.

Convenience accessor equivalent to active().system. Replaces the pre-v2.0 active() semantics.

active_formulas()

Return the active :class:~ucon.formulas.FormulaRegistry.

Convenience accessor equivalent to active().formulas.

active_kinds()

Return the active :class:~ucon.kinds.KindLattice.

Convenience accessor equivalent to active().kinds.

active_strict()

Return the active source-unit resolution mode.

Convenience accessor equivalent to active().strict.

use(system, *, formulas=None, kinds=None, strict=None)

Set the active :class:ActiveContext for the with-block.

Constructs an :class:ActiveContext with the given system and inherits formulas, kinds, and strict from the enclosing context when those kwargs are not supplied. On exit the prior context is restored.

Parameters

system : UnitSystem The system to activate for the duration of the with-block. formulas : FormulaRegistry, optional Override the formulas registry. Inherits from the enclosing context when None. kinds : KindLattice, optional Override the kind lattice. Inherits from the enclosing context when None. strict : bool, optional Override source-unit resolution mode. Inherits from the enclosing context when None.

Examples

sys = active_system() with use(sys) as ctx: ... assert ctx.system is sys