Skip to content

Releases: withtwoemms/ucon

v1.6.3

15 Apr 17:31
1.6.3
be82010

Choose a tag to compare

Added

  • Cross-basis coercion in @enforce_dimensions. When a CGS-basis argument
    (e.g. dyne, erg, poise) passes dimensional validation against an SI
    constraint, the decorator now automatically coerces it to the coherent SI
    equivalent before the function body runs. This eliminates "Cannot multiply
    dimensions from different bases" errors inside decorated functions.

v1.6.3a1

15 Apr 16:28
1.6.3a1
c62466e

Choose a tag to compare

v1.6.3a1 Pre-release
Pre-release
first alpha release of v1.6.3

v1.6.2

15 Apr 03:35
1.6.2
ae8e495

Choose a tag to compare

Added

  • Base-form conversion fallback. ConversionGraph._convert_via_base_form()
    decomposes both source and destination UnitProduct expressions to their SI
    base units and computes the conversion factor as the ratio of prefactors.
    This handles conversions where factor structures don't align (e.g.,
    kg·m/s² → N, J/L → Pa, W/m² → BTU/(h·ft²)) without requiring
    explicit product edges — the definitional identity is already encoded in
    each unit's base_form.

  • Cross-basis dimensional compatibility for @enforce_dimensions.
    _dimensions_compatible() in ucon/checking.py normalizes both actual and
    expected dimensions to SI via BasisGraph before comparing. CGS units like
    poise (dimension cgs_dynamic_viscosity) now satisfy constraints expecting
    the SI equivalent (dynamic_viscosity).

  • 22 new base_form entries in comprehensive.ucon.toml for SI-basis
    units that previously lacked decompositions: acre, ampere_per_meter, barn,
    becquerel, coulomb_meter, curie, farad, gray, hectare, henry,
    joule_per_kelvin, knot, lumen, mile_per_hour, molar, rad_dose, rem,
    siemens, sievert, tesla, weber, webers_per_meter.

Fixed

  • Liter base_form prefactor corrected from 1.0 to 0.001
    (1 L = 0.001 m³).

  • Tex base_form prefactor corrected from 9.0 to 1e-6
    (1 tex = 1 g/km = 1e-6 kg/m).

  • Denier base_form prefactor corrected from 1.0 to
    1.1111111111111111e-07 (1 denier = 1 g/9000 m).

  • 23 volume unit base_form prefactors corrected. All were calibrated
    relative to liter = 1.0 (wrong) instead of m³. Affected units: acre_foot,
    barrel, bushel, cubic_foot, cubic_inch, cubic_yard, cup, fluid_ounce,
    gallon, gill, imperial_cup, imperial_fluid_ounce, imperial_gallon,
    imperial_gill, imperial_pint, imperial_quart, minim, peck, pint, quart,
    stere, tablespoon, teaspoon.

  • generate_base_forms.py BFS oracle no longer assumes all reference
    units are SI-coherent (prefactor = 1.0). The BFS seed prefactor is now
    read from the unit's own TOML base_form, making the oracle a consistency
    checker across each dimension partition rather than relying on a hardcoded
    override table.

  • _dimensions_compatible crash on isolated bases. The except clause
    in _dimensions_compatible() did not catch NoTransformPath (raised by
    BasisGraph.get_transform() for disconnected bases), causing an unhandled
    exception instead of returning False.

v1.6.1

14 Apr 09:10
1.6.1
083c449

Choose a tag to compare

Fixed

  • Unit expression parser unified to standard left-to-right associativity.
    _UnitParser (the recursive-descent parser in ucon/parsing.py) was using a
    non-standard "slash-opens-denominator" convention where a/b*c was parsed as
    a/(b·c). Reverted to standard order of operations: * and / have equal
    precedence and associate left-to-right, so a/b*c = (a/b)·c. Multi-term
    denominators require explicit parentheses: m³/(kg·s²).

  • Gravitational constant (G), molar gas constant (R), and Stefan-Boltzmann
    constant (σ) unit strings
    in comprehensive.ucon.toml restored to use
    explicit parentheses: m³/(kg·s²), J/(mol·K), W/(m²·K⁴). Without
    parentheses, left-to-right parsing produced incorrect unit decompositions.

  • TOML exponent formatting. The serializer emitted ^2.0 for integral
    exponents; now emits ^2. The parser also now accepts float exponents
    (^2.0) for backward compatibility with previously-emitted TOML files.

Removed

  • _parse_product_expression() and _resolve_single_factor() from
    ucon/serialization.py. Product expression parsing is now handled
    exclusively by _UnitParser via get_unit_by_name(), eliminating a
    redundant regex-based parser that had diverged in associativity semantics.

v1.6.1a1

14 Apr 09:00
1.6.1a1
28adf6b

Choose a tag to compare

v1.6.1a1 Pre-release
Pre-release
first alpha release of v1.6.1a1

v1.6.0

12 Apr 18:49
1.6.0
ca0fd0f

Choose a tag to compare

Added

  • TOML as single source of truth. ucon/comprehensive.ucon.toml is now
    the canonical declaration site for all units, conversion edges, and physical
    constants. Python modules (ucon/units.py, ucon/graph.py,
    ucon/constants.py) consume the TOML at import time via a central
    single-load cache (ucon/_loader.py). ~1,250 lines of hardcoded Python
    declarations replaced by ~150 lines of loader infrastructure.

  • ucon/_loader.py — central TOML loader with caching. get_graph(),
    get_units(), get_constants() return the same object instances across
    all consumers. reset() for test isolation.

  • ucon/expressions.py — AST-based safe expression evaluator for TOML
    factor fields. Evaluates symbolic expressions like "1 / Eₕ" and
    "mP * c**2 / Eₕ" where symbols resolve to physical constants. Propagates
    relative uncertainty via GUM rules (quadrature for multiply/divide,
    |n| * rel for power, absolute quadrature for add/subtract). Only
    ast.Constant, ast.Name, ast.BinOp, and ast.UnaryOp nodes are
    accepted — no eval().

  • Symbolic expression factors in TOML. 14 conversion edges now use
    constant expression strings instead of hardcoded numeric values:

    • factor = "gₙ" for kilogram_force → newton, millimeter_water → pascal
    • factor = "1 / Eₕ" for joule → hartree
    • factor = "1 / Ry" for joule → rydberg
    • factor = "1 / e" for joule → electron_volt
    • factor = "1 / a₀" for meter → bohr_radius
    • factor = "1 / mP", "1 / lP", "1 / tP", "1 / TP" for
      SI → Planck unit edges
    • factor = "e / Eₕ", "e / (mP * c**2)", "mP * c**2 / Eₕ" for
      compound cross-basis edges
    • rel_uncertainty auto-derived from referenced constants via GUM
      quadrature — no longer hardcoded on these edges.
  • Auto-generated .pyi type stubs for IDE autocomplete:

    • ucon/units.pyi — all unit names typed as Unit
    • ucon/constants.pyi — all constant instances and accessor functions
    • Generated by scripts/generate_unit_stubs.py and
      scripts/generate_constant_stubs.py
  • Makefile targets for stub generation:

    • make stubs — generate all stubs (units, constants, dimensions)
    • make unit-stubs, make constant-stubs, make dimension-stubs
      individual targets
    • make stubs-check — verify all stubs are current (for CI)
  • stubs CI job (.github/workflows/tests.yaml) — verifies .pyi
    stubs match the current TOML on every push and PR. Wired into the
    ci gate job.

  • tests/ucon/test_expressions.py — expression parser tests covering
    numeric literals, constant references, division, multiplication, power,
    compound expressions, unknown symbols, and unsupported syntax.

  • tests/ucon/test__loader.py — loader integration tests covering graph
    construction, unit extraction, constant extraction, object identity
    guarantees, and cache behavior.

Changed

  • ucon/units.py rewritten — ~400 lines of hardcoded Unit()
    declarations removed, replaced by a thin loader that populates module
    globals from the TOML. register_priority_scaled_alias() calls,
    UnitSystem definitions, and have() preserved.

  • ucon/graph.py trimmed_build_standard_edges() (~557 lines of
    hardcoded graph.add_edge() calls) removed. _build_standard_graph()
    delegates to _loader.get_graph().

  • ucon/constants.py rewritten_build_constants() (~300 lines of
    hardcoded Constant() instances) removed, replaced by loader delegation.
    Constant gains an aliases: tuple = () field for TOML round-trip
    fidelity.

  • ucon/serialization.py enhanced — constants materialized before edges
    (step 7, was step 10) so expression factors can resolve constant symbols.
    _build_edge_map() accepts string expression factors containing constant
    references. FORMAT_VERSION bumped to "1.4". NFKC-normalized constant
    symbols registered in the lookup table for Python AST compatibility.

  • ucon/comprehensive.ucon.toml moved into package — canonical copy
    now at ucon/comprehensive.ucon.toml, shipped as package data. Includes
    26 constants with full CODATA metadata, 14 expression-based edge factors,
    no [contexts.*] section (contexts stay in Python).

  • Makefile stubs target expanded from dimension-only to all three
    stub types (units, constants, dimensions). Old toml target removed.

Removed

  • scripts/generate_comprehensive_toml.py — the old Python → TOML export
    script. The TOML is now hand-edited as the source of truth.

  • _build_standard_edges() body in ucon/graph.py (retained as no-op
    stub for backward compatibility).

  • _build_constants() body in ucon/constants.py.

  • Hardcoded Unit() declarations in ucon/units.py.

  • make toml Makefile target.

Notes

  • Backward compatibility. All public APIs (from ucon.units import meter,
    from ucon.constants import speed_of_light, get_default_graph(), etc.)
    work identically. The 2,095-test suite passes without modification.

  • Object identity guarantee. All consumers share the same Unit and
    Constant instances from a single TOML parse via _loader.py. E.g.,
    units.meter is get_default_graph()._name_registry_cs["meter"].

  • Import performance. The ~4,600-line TOML is parsed once at first
    import and cached. If performance becomes a concern, a pre-compiled
    binary cache can be added in a future release.

v1.5.0

10 Apr 16:55
1.5.0
7621482

Choose a tag to compare

Added

  • Conversion factor uncertainty propagation. When a conversion factor
    derives from a measured physical constant (e.g., Hartree energy, Planck
    mass), its CODATA 2022 relative uncertainty can now propagate into the
    converted result via GUM quadrature:
    (δy/y)² = (δx/x)² + (δa/a)².

  • rel_uncertainty field on Map subclasses — optional
    rel_uncertainty: float = 0.0 on LinearMap, AffineMap, and
    ReciprocalMap. Composition rules:

    • @ (composition): quadrature sqrt(r₁² + r₂²)
    • inverse(): preserved unchanged
    • **n (power): |n| * r
    • ComposedMap: computed property via quadrature of outer and inner
    • Default 0.0 means exact conversions carry zero overhead.
  • Number.to(target, propagate_factor_uncertainty=False) — opt-in
    parameter. When True, combines measurement uncertainty and conversion
    factor uncertainty via GUM quadrature. When False (default), behavior
    is unchanged from prior versions.

  • 8 new physical constants in ucon.constants with CODATA 2022
    uncertainties:

    • Atomic-scale: hartree_energy (Eₕ), rydberg_energy (Ry),
      bohr_radius (a₀), atomic_unit_of_time (ℏ/Eₕ)
    • Planck-scale: planck_mass (m_P), planck_length (l_P),
      planck_time (t_P), planck_temperature (T_P)
    • All carry category="measured" and are accessible via
      get_constant_by_symbol() with both Unicode and ASCII aliases.
  • 15 default graph edges with rel_uncertainty from measured constants:

    • Atomic: kg↔electron_mass, J↔hartree, eV↔hartree, J↔rydberg,
      m↔bohr_radius, s↔atomic_time, electron_mass→hartree,
      bohr_radius→atomic_time
    • Planck: kg↔planck_mass, J↔planck_energy, eV↔planck_energy,
      m↔planck_length, s↔planck_time, K↔planck_temperature,
      planck_energy↔hartree
  • EdgeDef.rel_uncertainty field in ucon.packages.ucon.toml
    packages can now declare conversion factor uncertainty on [[edges]]
    entries.

  • TOML serialization of rel_uncertainty. _edge_dict() emits
    rel_uncertainty when non-zero; _build_edge_map() reads it back.
    Backward-compatible: existing TOML files without the field deserialize
    with rel_uncertainty=0.0.

  • to_toml() dimension collection from unit registry. Dimensions
    referenced by registered units (e.g., area, velocity, force) are now
    included in the output even when they have no dedicated edge partition.
    Required for self-contained TOML files that don't rely on a runtime
    dimension registry — preparatory for the TOML-as-source-of-truth
    transition.

  • scripts/generate_comprehensive_toml.py — generation script that
    produces examples/units/comprehensive.ucon.toml from the default graph
    via to_toml(), with cosmetic array collapsing for readability.

  • make toml — Makefile target that runs the TOML generation script.

  • tests/ucon/test_factor_uncertainty.py — 32 new tests across 8 classes
    covering map construction, composition rules (quadrature, inverse,
    power), Number.to() backward compatibility, factor uncertainty
    propagation, multi-hop accumulation, and serialization round-trips.

  • tests/ucon/conversion/test_map.py::TestRelUncertaintyComposition
    6 new tests for rel_uncertainty composition, inverse, and power on
    LinearMap and AffineMap.

Changed

  • comprehensive.ucon.toml is now machine-generated from the default
    graph rather than hand-maintained. Contains 7 bases, 71 dimensions,
    15 transforms, 227 units, 148 edges, 17 product edges, and 42
    cross-basis edges (15 with rel_uncertainty).

  • ucon.constants used as single source for conversion factors in
    ucon.graph.
    The default graph's _build_standard_edges() now loads
    full Constant objects and extracts both .value and .uncertainty,
    rather than using hard-coded numeric literals. This ensures conversion
    factor values and their uncertainties stay in sync with the constants
    module.

Notes

  • Backward compatibility. propagate_factor_uncertainty defaults to
    False. All existing code produces identical results. The
    rel_uncertainty field defaults to 0.0 and is omitted from TOML
    output when exact, so serialized files remain unchanged for exact
    conversions.

  • GUM model. The implementation uses relative uncertainty because it
    composes cleanly under multiplication and is preserved under inversion.
    For affine maps (temperature), rel_uncertainty refers to the slope
    a only; the offset b is exact by definition.

v1.5.0a1

10 Apr 15:58
1.5.0a1
064850d

Choose a tag to compare

v1.5.0a1 Pre-release
Pre-release
first v1.5.0 alpha release

v1.4.0

10 Apr 02:34
1.4.0
7ad4813

Choose a tag to compare

Added

  • Planck basis — 1-component energy basis (E) where ℏ = c = G = k_B = 1.

    • PLANCK basis in ucon.basis.builtin
    • SI_TO_PLANCK / PLANCK_TO_SI transforms via ConstantBoundBasisTransform
      with constant bindings for ℏ, c, G, k_B
    • PLANCK_ENERGY (E¹) and PLANCK_LENGTH (E⁻¹) dimensions
    • 5 Planck units: planck_energy (E_P), planck_mass (m_P),
      planck_length (l_P), planck_time (t_P),
      planck_temperature (T_P) — all with CODATA 2018 values
    • Mass and energy share PLANCK_ENERGY (E¹); length and time share
      PLANCK_LENGTH (E⁻¹). This is physically correct: when c = 1,
      mass ≡ energy and length ≡ time.
  • Atomic basis — 1-component energy basis (E) where ℏ = e = mₑ = 4πε₀ = 1.

    • ATOMIC basis in ucon.basis.builtin
    • SI_TO_ATOMIC / ATOMIC_TO_SI transforms with constant bindings
      for a₀, ℏ, mₑc², e/ℏ
    • ATOMIC_ENERGY (E¹) and ATOMIC_LENGTH (E⁻¹) dimensions
    • 3 new atomic units: bohr_radius (a_0, a0), atomic_time (t_au),
      electron_mass (m_e)
    • Differs from Natural/Planck in that electric current is representable
      (I → E¹) but temperature is not (k_B ≠ 1)
  • Inter-basis isomorphisms — 6 bidirectional 1×1 identity transforms
    connecting NATURAL, PLANCK, and ATOMIC bases:

    • NATURAL_TO_PLANCK / PLANCK_TO_NATURAL (mediated by G)
    • NATURAL_TO_ATOMIC / ATOMIC_TO_NATURAL (mediated by e, mₑ, 4πε₀)
    • PLANCK_TO_ATOMIC / ATOMIC_TO_PLANCK (mediated by G, e, mₑ, 4πε₀)
    • Cross-basis conversion edges: eV ↔ planck_energy,
      eV ↔ hartree, planck_energy ↔ hartree
    • Inter-basis edge factors are computed from shared SI bridge constants
      (e.g., _eV_J / _EP_J) rather than independently rounded, ensuring
      exact algebraic cancellation on round-trips.
  • CGS-EMU basis promotionCGS_EMU promoted from 3-component
    (L, M, T) to 4-component (L, M, T, Φ) basis to support the
    ESU↔EMU bridge:

    • SI_TO_CGS_EMU is now an 8×4 transform with I → Φ¹ mapping
    • CGS_ESU_TO_CGS_EMU and CGS_EMU_TO_CGS_ESU bridge transforms
      (4×4) connecting the two electromagnetic subsystems
    • 15 ESU/EMU dimension vectors redefined with integer exponents on
      the expanded bases
    • ESU↔EMU conversion edges via the speed of light c:
      statcoulomb ↔ abcoulomb, statvolt ↔ abvolt,
      statampere ↔ biot, statohm ↔ abohm,
      statfarad ↔ abfarad, stathenry ↔ abhenry
    • Fulfills the v1.3.1 deferral: "ESU↔EMU cross-family conversion
      deferred to v1.4.0"
  • 33+ new tests across test_cross_basis.py:
    TestPlanckDimensionIsolation (5), TestPlanckConversions (6),
    TestAtomicDimensionIsolation (7), TestAtomicConversions (6),
    TestInterBasisIsomorphisms (6 including full J→E_P→eV→Eₕ→J
    round-trip at places=10), plus ESU↔EMU bridge tests.

Changed

  • hartree and rydberg moved from NATURAL to ATOMIC basis.
    These units physically belong to the atomic system (ℏ = e = mₑ =
    4πε₀ = 1), not the particle-physics natural system. Their dimension
    changes from NATURAL_ENERGY to ATOMIC_ENERGY; numeric conversion
    values to SI are unchanged. Cross-basis edges from SI
    (joule → hartree, joule → rydberg) now route through
    SI_TO_ATOMIC instead of SI_TO_NATURAL.

  • BasisGraph standard graph now registers 15 transforms (was 6):
    SI↔CGS, SI↔CGS_ESU, SI↔CGS_EMU, CGS_ESU↔CGS_EMU, SI↔NATURAL,
    SI↔PLANCK, SI↔ATOMIC, NATURAL↔PLANCK, NATURAL↔ATOMIC, PLANCK↔ATOMIC.

Notes

  • Round-trip precision. The full cross-basis round-trip
    joule → planck_energy → electron_volt → hartree → joule returns
    exactly 1.0 (verified at places=10). This is achieved by defining
    physical constants once and computing inter-basis factors from those
    shared values, avoiding independently-rounded intermediate constants.

  • Dimension sharing on reduced bases. On any 1-component energy
    basis, units mapping to E¹ (energy, mass, temperature) share one
    Dimension object, and units mapping to E⁻¹ (length, time) share
    another. This is not a collision — it encodes the physics of
    c = ℏ = 1. Consequently, planck_mass(1).to(planck_energy) → 1
    and planck_length(1).to(planck_time) → 1 are both valid
    conversions.

v1.3.1

09 Apr 19:50
1.3.1
06378a4

Choose a tag to compare

Added

  • Photometric luminance units — 4 new SI-basis ILLUMINANCE units:

    • nit (nt) — 1 cd/m², the SI-coherent luminance unit
    • stilb (sb) — 1 cd/cm² = 10,000 cd/m²
    • lambert (La) — (1/π) cd/cm² ≈ 3183.1 cd/m²
    • apostilb (asb) — (1/π) cd/m² ≈ 0.3183 cd/m²

    All four carry base_form with prefactor relative to cd·m⁻² and
    same-basis conversion edges (nit→lux, stilb→nit, lambert→nit,
    apostilb→nit). No cross-basis edges needed — these are SI-basis
    units because their dimensional formula involves candela, which
    belongs exclusively to the SI basis (CGS has no luminous intensity
    component).

  • TestPhotometricConversions — 6 new tests covering stilb↔nit,
    lambert→nit, apostilb→nit, stilb→lux (multi-hop), and
    phot→stilb (cross-validation).

  • Disposition comment on phot in ucon/units.py explaining why it
    uses SI-basis ILLUMINANCE despite being conventionally called "CGS".

  • Deferral comment on ESU↔EMU cross-family conversion in ucon/graph.py,
    noting that the bridge requires promoting CGS_EMU to a 4-component
    basis and is scheduled for v1.4.0 (basis isomorphisms release). See
    docs/internal/IMPLEMENTATION_basis_isomorphisms.md.

Notes

  • Cross-basis edge audit (24/24 CGS→SI, 7/7 SI→CGS). All atomic
    CGS-family units were verified to have correct bidirectional edges in
    the default graph. No missing edges found.

  • ESU↔EMU cross-family conversion deferred to v1.4.0. Requires
    promoting CGS_EMU to a 4-component basis (L, M, T, Φ),
    redefining ~15 dimension vectors, and adding a quantity-dependent
    CGS_ESU_TO_CGS_EMU 4×4 transform. This is a refactoring-scale
    change best done while the drift detector is still active as a safety
    net.