All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- 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.
-
Base-form conversion fallback.
ConversionGraph._convert_via_base_form()decomposes both source and destinationUnitProductexpressions 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'sbase_form. -
Cross-basis dimensional compatibility for
@enforce_dimensions._dimensions_compatible()inucon/checking.pynormalizes both actual and expected dimensions to SI viaBasisGraphbefore comparing. CGS units like poise (dimensioncgs_dynamic_viscosity) now satisfy constraints expecting the SI equivalent (dynamic_viscosity). -
22 new
base_formentries incomprehensive.ucon.tomlfor 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.
-
Liter
base_formprefactor corrected from1.0to0.001(1 L = 0.001 m³). -
Tex
base_formprefactor corrected from9.0to1e-6(1 tex = 1 g/km = 1e-6 kg/m). -
Denier
base_formprefactor corrected from1.0to1.1111111111111111e-07(1 denier = 1 g/9000 m). -
23 volume unit
base_formprefactors 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.pyBFS 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 TOMLbase_form, making the oracle a consistency checker across each dimension partition rather than relying on a hardcoded override table. -
_dimensions_compatiblecrash on isolated bases. The except clause in_dimensions_compatible()did not catchNoTransformPath(raised byBasisGraph.get_transform()for disconnected bases), causing an unhandled exception instead of returningFalse.
1.6.1 - 2026-04-13
-
Unit expression parser unified to standard left-to-right associativity.
_UnitParser(the recursive-descent parser inucon/parsing.py) was using a non-standard "slash-opens-denominator" convention wherea/b*cwas parsed asa/(b·c). Reverted to standard order of operations:*and/have equal precedence and associate left-to-right, soa/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.tomlrestored 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.0for integral exponents; now emits^2. The parser also now accepts float exponents (^2.0) for backward compatibility with previously-emitted TOML files.
_parse_product_expression()and_resolve_single_factor()fromucon/serialization.py. Product expression parsing is now handled exclusively by_UnitParserviaget_unit_by_name(), eliminating a redundant regex-based parser that had diverged in associativity semantics.
1.6.0 - 2026-04-12
-
TOML as single source of truth.
ucon/comprehensive.ucon.tomlis 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| * relfor power, absolute quadrature for add/subtract). Onlyast.Constant,ast.Name,ast.BinOp, andast.UnaryOpnodes are accepted — noeval(). -
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 → pascalfactor = "1 / Eₕ"for joule → hartreefactor = "1 / Ry"for joule → rydbergfactor = "1 / e"for joule → electron_voltfactor = "1 / a₀"for meter → bohr_radiusfactor = "1 / mP","1 / lP","1 / tP","1 / TP"for SI → Planck unit edgesfactor = "e / Eₕ","e / (mP * c**2)","mP * c**2 / Eₕ"for compound cross-basis edgesrel_uncertaintyauto-derived from referenced constants via GUM quadrature — no longer hardcoded on these edges.
-
Auto-generated
.pyitype stubs for IDE autocomplete:ucon/units.pyi— all unit names typed asUnitucon/constants.pyi— all constant instances and accessor functions- Generated by
scripts/generate_unit_stubs.pyandscripts/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 targetsmake stubs-check— verify all stubs are current (for CI)
-
stubsCI job (.github/workflows/tests.yaml) — verifies.pyistubs match the current TOML on every push and PR. Wired into thecigate 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.
-
ucon/units.pyrewritten — ~400 lines of hardcodedUnit()declarations removed, replaced by a thin loader that populates module globals from the TOML.register_priority_scaled_alias()calls,UnitSystemdefinitions, andhave()preserved. -
ucon/graph.pytrimmed —_build_standard_edges()(~557 lines of hardcodedgraph.add_edge()calls) removed._build_standard_graph()delegates to_loader.get_graph(). -
ucon/constants.pyrewritten —_build_constants()(~300 lines of hardcodedConstant()instances) removed, replaced by loader delegation.Constantgains analiases: tuple = ()field for TOML round-trip fidelity. -
ucon/serialization.pyenhanced — 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_VERSIONbumped to"1.4". NFKC-normalized constant symbols registered in the lookup table for Python AST compatibility. -
ucon/comprehensive.ucon.tomlmoved into package — canonical copy now atucon/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
stubstarget expanded from dimension-only to all three stub types (units, constants, dimensions). Oldtomltarget 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 inucon/graph.py(retained as no-op stub for backward compatibility). -
_build_constants()body inucon/constants.py. -
Hardcoded
Unit()declarations inucon/units.py. -
make tomlMakefile target.
-
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
UnitandConstantinstances 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.
1.5.0 - 2026-04-10
-
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_uncertaintyfield onMapsubclasses — optionalrel_uncertainty: float = 0.0onLinearMap,AffineMap, andReciprocalMap. Composition rules:@(composition): quadraturesqrt(r₁² + r₂²)inverse(): preserved unchanged**n(power):|n| * rComposedMap: computed property via quadrature ofouterandinner- Default
0.0means exact conversions carry zero overhead.
-
Number.to(target, propagate_factor_uncertainty=False)— opt-in parameter. WhenTrue, combines measurement uncertainty and conversion factor uncertainty via GUM quadrature. WhenFalse(default), behavior is unchanged from prior versions. -
8 new physical constants in
ucon.constantswith 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 viaget_constant_by_symbol()with both Unicode and ASCII aliases.
- Atomic-scale:
-
15 default graph edges with
rel_uncertaintyfrom 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_uncertaintyfield inucon.packages—.ucon.tomlpackages can now declare conversion factor uncertainty on[[edges]]entries. -
TOML serialization of
rel_uncertainty._edge_dict()emitsrel_uncertaintywhen non-zero;_build_edge_map()reads it back. Backward-compatible: existing TOML files without the field deserialize withrel_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 producesexamples/units/comprehensive.ucon.tomlfrom the default graph viato_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 forrel_uncertaintycomposition, inverse, and power onLinearMapandAffineMap.
-
comprehensive.ucon.tomlis 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 withrel_uncertainty). -
ucon.constantsused as single source for conversion factors inucon.graph. The default graph's_build_standard_edges()now loads fullConstantobjects and extracts both.valueand.uncertainty, rather than using hard-coded numeric literals. This ensures conversion factor values and their uncertainties stay in sync with the constants module.
-
Backward compatibility.
propagate_factor_uncertaintydefaults toFalse. All existing code produces identical results. Therel_uncertaintyfield defaults to0.0and 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_uncertaintyrefers to the slopeaonly; the offsetbis exact by definition.
1.4.0 - 2026-04-09
-
Planck basis — 1-component energy basis (
E) where ℏ = c = G = k_B = 1.PLANCKbasis inucon.basis.builtinSI_TO_PLANCK/PLANCK_TO_SItransforms viaConstantBoundBasisTransformwith constant bindings for ℏ, c, G, k_BPLANCK_ENERGY(E¹) andPLANCK_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 sharePLANCK_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.ATOMICbasis inucon.basis.builtinSI_TO_ATOMIC/ATOMIC_TO_SItransforms with constant bindings for a₀, ℏ, mₑc², e/ℏATOMIC_ENERGY(E¹) andATOMIC_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 promotion —
CGS_EMUpromoted from 3-component (L, M, T) to 4-component (L, M, T, Φ) basis to support the ESU↔EMU bridge:SI_TO_CGS_EMUis now an 8×4 transform withI → Φ¹mappingCGS_ESU_TO_CGS_EMUandCGS_EMU_TO_CGS_ESUbridge 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 atplaces=10), plus ESU↔EMU bridge tests.
-
hartreeandrydbergmoved 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 fromNATURAL_ENERGYtoATOMIC_ENERGY; numeric conversion values to SI are unchanged. Cross-basis edges from SI (joule → hartree,joule → rydberg) now route throughSI_TO_ATOMICinstead ofSI_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.
-
Round-trip precision. The full cross-basis round-trip
joule → planck_energy → electron_volt → hartree → joulereturns exactly 1.0 (verified atplaces=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
Dimensionobject, 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 andplanck_length(1).to(planck_time)→ 1 are both valid conversions.
1.3.1 - 2026-04-09
-
Photometric luminance units — 4 new SI-basis ILLUMINANCE units:
nit(nt) — 1 cd/m², the SI-coherent luminance unitstilb(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_formwithprefactorrelative tocd·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 involvescandela, which belongs exclusively to the SI basis (CGS has no luminous intensity component). -
TestPhotometricConversions— 6 new tests coveringstilb↔nit,lambert→nit,apostilb→nit,stilb→lux(multi-hop), andphot→stilb(cross-validation). -
Disposition comment on
photinucon/units.pyexplaining 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 promotingCGS_EMUto a 4-component basis and is scheduled for v1.4.0 (basis isomorphisms release). Seedocs/internal/IMPLEMENTATION_basis_isomorphisms.md.
-
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_EMUto a 4-component basis (L, M, T, Φ), redefining ~15 dimension vectors, and adding a quantity-dependentCGS_ESU_TO_CGS_EMU4×4 transform. This is a refactoring-scale change best done while the drift detector is still active as a safety net.
1.3.0 - 2026-04-08
-
BaseForm— a new dataclass atucon.core.BaseForm(re-exported fromucon) representing a unit's definitional decomposition into the canonical base units of its basis:1 U ≡ prefactor × b₁^e₁ × b₂^e₂ × ... × bₙ^eₙBaseFormis immutable, dimensionally consistent with its parentUnit, and references only base units of that unit's own basis. -
Unit.base_form— new public attribute onUnit, set at construction and never overwritten thereafter. Carried by 137 atomic units inucon/units.py: 129 via constructor literal plus 8 via a one-shotUnit._set_base_formbootstrap (one per unit) for the self-referential SI base units (kilogram,meter,second,ampere,kelvin,candela,mole,bit), whosebase_formcannot be expressed as a constructor literal because each references itself. The 4 affine non-base temperature units (celsius,fahrenheit,rankine,reaumur) carrybase_form = Nonebecausey = a·x + bcannot be represented as a single(prefactor, factors)pair. -
Number.to_base()— new public method that returns a newNumberexpressed in the basis's coherent base units (e.g., SI:kg, m, s, A, K, cd, mol). It decomposes each factor ofself.unitthrough itsbase_formand folds scale prefixes, without consulting anyConversionGraph. Units that lack abase_form(affine temperature, logarithmic, or graph-only units) are preserved as-is. Uncertainty is scaled by the same multiplier as the quantity. Examples:kilometer(5).to_base()→<5000.0 m>;(kilometer(90) / hour(1)).to_base()→<25.0 m/s>;joule(1).to_base()→<1.0 m²·kg/s²>. -
Number.canonical_magnitude— new public property that returnsself._canonical_magnitudeas a plain float. Useful at interop boundaries where a raw SI-coherent magnitude is needed (formula constants, JSON payloads, plotting libraries). The identityn.to_base().quantity == n.canonical_magnitudeholds for everyNumber n. Preferto_base()for unit-safe composition. -
tests/ucon/test_quantity.py::TestNumberCanonicalBaseForm— 19 new tests covering scaled units, compound units, derived units with multi-factorbase_form, self-referential coherent bases, units withbase_form = None, zero-quantity uncertainty propagation, and theto_base().quantity == canonical_magnitudeidentity. -
Unit._set_base_form(bf)— the single sanctioned post-construction mutation point forbase_form. Guards against re-assignment (ValueError) and non-BaseForminputs (TypeError). Used by the SI bootstrap inucon/units.pyand by the TOML deserializer inucon/serialization.py; no other caller mutatesbase_form. -
TOML serialization of
base_form.to_tomlnow emits each unit'sbase_formas an inline dict on the[[units]]entry:base_form = { prefactor = 1.0, factors = [ [ "meter", 1.0 ], [ "kilogram", 1.0 ], [ "second", -2.0 ] ] }from_tomlresolves the factor references in a second pass after all units are registered, so forward references between units in the same document are supported. Affine and logarithmic units whosebase_formisNoneomit the key.examples/units/comprehensive.ucon.tomlships with 134base_formentries, round-trip verified. -
TestRoundTrip::test_base_form_roundtrip— asserts that every non-RebasedUnit'sbase_form(both theNonecase and the populated case) survives TOML export/import bit-for-bit. Necessary becauseUnit.__eq__ignoresbase_form(compare=False), sotest_graph_equalityalone cannot detect a dropped or corruptedbase_form. -
Graph-independent quantity arithmetic.
Numberequality, comparison, and arithmetic now route throughNumber._canonical_magnitude, a pure function of(quantity, unit)that readsbase_formdirectly and never consults aConversionGraph. Importingucon.unitsand evaluatinggram(1000) == kilogram(1), dimensional ratios, and similar expressions no longer requires the default graph to be built. This is enforced bytests/ucon/test_base_form.py::TestNoGraphInit::test_cold_start_subprocess, which runs a subprocess-isolated smoke test against a fresh interpreter. -
scripts/generate_base_forms.py— a drift detector that compares the hand-writtenbase_formliterals inucon/units.pyagainst an internal BFS oracle computed over the standard conversion graph. Modes:--check(CI gate),--report(human-readable diff),--emit(regenerate literals). (Scheduled to be superseded in v1.4.0 by aucon.tomlcatalog validator. The drift dimension changes — from "hand-written literal vs. graph oracle" to "catalog TOML parseability, structural validity, and round-trip integrity" — but the pre-release CI gate remains: no malformed catalog reaches a tag.) -
make base-forms-check— Makefile target wiring the drift detector into CI. -
base_formsCI job (.github/workflows/tests.yaml) — single-version GitHub Actions job (Python 3.12) that runsmake base-forms-checkon every push tomainand every pull request, and is included in the aggregatingcijob'sneeds:list so branch-protected merges are blocked on drift. This closes a gap in the pytest-based suite:Unit.__eq__usescompare=Falseforbase_form, so neithertest_graph_equalitynortest_base_form_roundtripcan detect a hand-edited literal inucon/units.pywhose prefactor has silently drifted from what the graph would compute; only the BFS oracle can. In v1.4.0 this job will be replaced (not removed) by aucon.tomlcatalog-validation job that asserts the shipped catalog parses, resolves all references, and round-trips cleanly throughto_toml/from_toml. The release-blocking invariant — "no malformed catalog reaches a tag" — persists across the transition; only the oracle changes. -
tests/ucon/test_base_form.py— 34 tests covering theBaseFormcontract, the graph-independence invariant, affine-unitNonehandling, and the cold-start subprocess smoke test. -
Unit.base_signature/UnitProduct.base_signature/Number.base_signature— new public properties returning a hashable, sorted tuple of(base_unit_name, exponent)pairs that fingerprint the unit's base-form decomposition. The prefactor is intentionally dropped:base_signatureidentifies the shape of a quantity (which base units participate, and with what exponents), not its scale. Useful as a dispatch key for grouping formula inputs by kind. The identityn.base_signature == n.to_base().base_signatureholds for everyNumber n. Examples:units.meter.base_signature → (("meter", 1.0),);units.joule.base_signature → (("kilogram", 1.0), ("meter", 2.0), ("second", -2.0)). Units withbase_form = None(affine temperature, logarithmic) report themselves as a self-leaf so the API is total. -
Number.in_base_form— new public property; a fast predicate for "is this Number already whatto_base()would return?" ReturnsTruewhen every factor is atScale.one, every underlyingUnitis a leaf (eitherbase_form is Noneor a self-referential coherent base), and any residual scale factor on aUnitProductis1.0. Useful as a hot-path guard against redundantto_base()calls and as an invariant assertion at formula boundaries. -
Number.same_dimension_as(other)— new public method that returnsTrueifselfandothershare aDimension. Accepts aNumber,Unit, orUnitProduct; raisesTypeErrorfor any other type. A lightweight, graph-free compatibility check for the common "can these be added / compared / fed into the same formula slot?" question, distinct fromUnit.is_compatiblewhich is unit-to-unit and basis-graph-aware. -
tests/ucon/test_quantity.py::TestBaseSignature,TestNumberInBaseForm, andTestNumberSameDimensionAs— 24 new tests covering coherent base units, derived units, prefactor independence, composition under arithmetic, theto_base()invariance identity, scaled-vs-unscaled distinctions, leaf-detection forbase_form = Noneunits, residual-scale-factor handling onUnitProduct, and theTypeErrorcontract onsame_dimension_as. -
ConversionGraph.add_edgedocumented as public API. The keyword-only signature (src,dst,map,basis_transform) was already in use but unmarked; v1.3.0 promotes it to a public, semver-stable surface with an expanded docstring covering the most common usage patterns (linear edges, affine temperature edges, composite-unit edges) and a cross-reference toConversionGraph.connect_systemsfor bulk cross-basis registration. No behavioral change.
FORMAT_VERSIONbumped from"1.2"to"1.3"inucon.serializationto mark the addition of the optionalbase_formfield on[[units]]entries. Older1.2files continue to load without warning (nobase_formdata to drop). Newer1.3files loaded by an older1.2library keep the same major version, so_check_format_versiondoes not raise; it emits aUserWarningthat the file is newer than supported, and anybase_formentries are silently dropped by the older deserializer. Downstream consumers who serialize their own.ucon.tomlfiles should bump theirformat_versionwhen they begin emittingbase_form.
Number.to(target)continues to route throughConversionGraph.convert()in both its fast path (plainUnit → Unit) and its general path (UnitProduct → UnitProduct). The graph remains load-bearing for everythingBaseFormstructurally cannot represent: affine temperature conversions, logarithmic and other non-linear conversions, cross-basis conversions (SI ↔ CGS ↔ natural viaRebasedUnitedges), user-registered custom edges, and uncertainty propagation viaMap.derivative(). This release decouples arithmetic from the graph; explicit conversion via.to()still uses it.
1.2.0 - 2026-04-06
- Full round-trip
ConversionGraphserialization to TOML (to_toml()/from_toml())- Bases, dimensions, and transforms (including
ConstantBoundBasisTransformwith fraction-exact matrices) - Unit edges with shorthand (
factor,factor+offset) and explicitmapfor all 6 map types - Product edges for composite unit conversions (e.g., kWh → joule)
- Cross-basis edges via
RebasedUnitprovenance (e.g., dyne → newton across CGS/SI) - Physical constants with full metadata (symbol, name, value, unit, uncertainty, source, category)
ConversionContextpersistence via[contexts.*]TOML sectionsgraph == restoredequality guaranteed for all graph types after round-trip
- Bases, dimensions, and transforms (including
GraphLoadErrorexception for structured error reporting during TOML import- Strict parsing mode on
from_toml()(defaultstrict=True)- Raises
GraphLoadErroron unresolvable unit references in edges, product edges, and cross-basis edges strict=Falsewarns and skips unresolvable edges for forward-compatible loading
- Raises
- Format version validation in
from_toml()- Major version mismatch raises
GraphLoadError - Newer minor version emits
UserWarning - Missing
format_versionor[package]table accepted for backward compatibility
- Major version mismatch raises
- Product expression grammar with
/division support- Parser:
meter/second,mg/kg/day,kg*m/s^2all parse correctly - Left-to-right arithmetic precedence (
*and/are left-associative) - Invalid exponents raise
GraphLoadError(previously returnedNone) - Emitter: uses
/notation when there are both positive and negative exponents
- Parser:
- Implicit
Mapsubclass discovery for TOML deserialization- Any imported
Mapsubclass with a_map_typeclass attribute is automatically deserializable _build_map()recursively resolves nested map specs (dicts with"type"keys)- Custom
Mapsubclasses need only define_map_typeand be imported beforefrom_toml()
- Any imported
Map.to_dict()base implementation with recursive serialization- Introspects dataclass fields, skips
_-prefixed attributes - Recursively serializes
Map-valued fields and lists containingMapinstances - Subclasses may override to omit default values (e.g.,
LogMap,ExpMap)
- Introspects dataclass fields, skips
_map_typeclass attribute on all concreteMapsubclasses:LinearMap("linear"),AffineMap("affine"),LogMap("log"),ExpMap("exp"),ReciprocalMap("reciprocal"),ComposedMap("composed")ConversionGraph.register_context()for attaching namedConversionContextobjects to a graphConversionGraph.__eq__with comprehensive field comparison- Symmetric unit-edge comparison across all dimension partitions
- Product-edge comparison by key set and map evaluation
- Cross-basis edge comparison via
_cross_basis_edge_signature() - Constants comparison across all metadata fields plus unit dimension
- Loaded packages, basis graph topology, and context equality
- Serialization format reference documentation (
docs/reference/serialization-format.md) ucon[serialization]optional dependency (tomli-w) for TOML export- Arithmetic expression factors in
from_toml()via_parse_factor()_build_edge_map()now accepts string factor expressions (e.g.,"1/3","1852/3600")- Parity with
load_package(), which already supported expression factors
- Production-ready
examples/units/comprehensive.ucon.toml- Complete default conversion graph: 4 bases, 43 dimensions, 214 units, 139 edges, 18 product edges, 26 cross-basis edges, 1 context
- Exact ratio factors where applicable (e.g.,
"1/3"for foot→yard,"1/7"for day→week) - Deduplicated cross-basis edges and cleaned empty unit entry from machine-generated export
ConversionGraph._rebasedchanged fromdict[Unit, list[RebasedUnit]]todict[Unit, set[RebasedUnit]]- Prevents duplicate
RebasedUnitaccumulation when multiple cross-basis edges share the same source unit and transform (e.g., joule → electron_volt/hartree/rydberg) - Eliminates cubic amplification of
[[cross_basis_edges]]on repeated round-trips list_rebased_units()return type unchanged (dict[Unit, list[RebasedUnit]])
- Prevents duplicate
- Product expression parsing (
_parse_product_expression) usesget_unit_by_name()as primary resolver- Scale-prefixed names (e.g.,
kwatt) now decompose correctly intoUnitFactorwith properScale - Falls back to
_resolve_unit()when the full resolver is unavailable
- Scale-prefixed names (e.g.,
- Map deserialization uses implicit subclass discovery instead of an explicit registry
_build_map()walksMap.__subclasses__()to find the class matching the"type"key- No registry to manage; defining
_map_typeon a subclass is sufficient
- Updated
docs/external/ucon-toolssubmodule from 0.2.1 to 0.3.2
1.1.2 - 2026-04-03
- Binary scale prefixes:
tebi(Ti, 2^40),pebi(Pi, 2^50),exbi(Ei, 2^60)- Enables parsing of
TiB,PiB,EiBand other tebi/pebi/exbi-scaled units - Greedy prefix matching ensures
TB/PBstill resolve as tera/peta (decimal)
- Enables parsing of
- Spelled-out scale aliases for common unit names via
register_priority_scaled_alias- Length:
kilometer,centimeter,millimeter,micrometer,nanometer,picometer - Mass:
milligram,microgram - Time:
millisecond,microsecond,nanosecond,picosecond - Frequency:
kilohertz,megahertz,gigahertz - Volume:
milliliter,microliter - Power:
kilowatt,megawatt,gigawatt,milliwatt - Energy:
kilojoule,megajoule - Pressure:
kilopascal,megapascal,hectopascal - Voltage:
millivolt,kilovolt - Current:
milliampere,microampere - Information (decimal):
kilobyte,megabyte,gigabyte,terabyte,petabyte,kilobit,megabit,gigabit - Information (binary):
kibibyte,mebibyte,gibibyte,tebibyte,pebibyte,exbibyte
- Length:
1.1.1 - 2026-04-02
- Cross-basis conversions for composite unit strings (e.g.,
poise → Pa·s,reyn → Pa·s)- Composite strings like
"Pa·s","m²/s","J/m²"were parsed as multi-factorUnitProducts rather than resolving to registered atomic unit aliases, causing cross-system (CGS↔SI) and same-dimension conversions to fail with spurious dimension mismatch or factor structure errors _convert_products()now resolves products to atomicUnitequivalents viaas_unit()and the graph's name registry before dimension comparison and factorwise decomposition- Affected units: poise, stokes, galileo, reyn, kayser, langley, oersted
- Composite strings like
UnitProduct.as_unit()method to extract the underlyingUnitfrom trivial single-factor products (one factor, exponent 1,Scale.one)
1.1.0 - 2026-04-01
[package]table in.ucon.tomlformat for structured package metadataname,version,description,requiresfields- Legacy top-level keys still supported with deprecation warning
shorthandfield onUnitDeffor explicit display symbols (e.g.,shorthand = "nmi")requiresfield onUnitPackagefor declaring package dependencies- Validated during
with_package()— raisesPackageLoadErrorif dependencies not loaded
- Validated during
[[constants]]section in.ucon.tomlfor domain-specific physical constantsConstantDefdataclass withsymbol,name,value,unit,uncertainty,source,category- Constants materialized onto graph during
with_package() - Accessible via
graph.package_constantsproperty
- Explicit
maptype on[[edges]]for non-linear conversion maps- Supported types:
linear,affine,log,exp,reciprocal map_specfield onEdgeDefwith_build_map()dispatchmaptakes precedence overfactor/offsetshorthand when both present
- Supported types:
ExpMapadded to package format map type registry- Verified composite unit expressions (e.g.,
"watt*hour","kg*m/s^2") resolve correctly assrc/dstinEdgeDef.materialize()— product edges work without format changes ConstantDefexported fromucontop-level package
1.0.0 - 2026-03-31
ReciprocalMap(a)conversion map for inversely proportional relationships (y = a / x)- Self-inverse:
ReciprocalMap(a).inverse()returnsReciprocalMap(a) - Used for spectroscopy conversions (e.g., frequency = c / wavelength)
- Self-inverse:
- EXPOSURE dimension (
I·T/M) and roentgen unit (R_exp) for radiation exposurecoulomb_per_kilogrambridge unit withC/kgalias- 1 R = 2.58e-4 C/kg conversion edge
- CGS-EMU electromagnetic unit system
SI_TO_CGS_EMUbasis transform mapping SI current toL^(1/2)·M^(1/2)·T^(-1)in CGS- 6 CGS-EMU dimensions:
CGS_EMU_CURRENT,CGS_EMU_CHARGE,CGS_EMU_VOLTAGE,CGS_EMU_RESISTANCE,CGS_EMU_CAPACITANCE,CGS_EMU_INDUCTANCE - 7 CGS-EMU units:
biot(abampere),abcoulomb,abvolt,abohm,abfarad,abhenry,gilbert - Cross-basis edges: ampere↔biot, coulomb↔abcoulomb, volt↔abvolt, ohm↔abohm, farad↔abfarad, henry↔abhenry
ConversionContextfor scoped cross-dimensional conversions (ucon/contexts.py)ContextEdgedataclass for cross-dimensional edge specificationsusing_context()context manager that copies the graph, injects context edges, and scopes viausing_graph()- Built-in
spectroscopycontext: wavelength/frequency/energy via c and h - Built-in
boltzmanncontext: temperature/energy via k_B - Cross-dimensional BFS fallback in
ConversionGraph._bfs_convert_cross_dimensional()
- Réaumur temperature scale (
reaumur, aliases:°Ré,degRe)- 1 °Ré = 1.25 °C conversion edge
- Historical electrical units
international_ampere(A_int): 1 A_int = 1.000022 Ainternational_volt(V_int): 1 V_int = 1.00034 Vinternational_ohm(ohm_int): 1 Ω_int = 1.00049 Ω
CyclicInconsistency,spectroscopy,boltzmann,register_unitexported from top-level package__all__declarations forucon.mapsanducon.graphSECURITY.mdvulnerability disclosure policySUPPORT.mdsemantic versioning, LTS, and backward-compatibility policy
ConversionGraph._rebasedchanged fromdict[Unit, RebasedUnit]todict[Unit, list[RebasedUnit]]- Fixes collision when multiple basis transforms register rebased entries for the same source unit (e.g., CGS-ESU and CGS-EMU both rebasing
ampere) list_rebased_units()now returnsdict[Unit, list[RebasedUnit]]
- Fixes collision when multiple basis transforms register rebased entries for the same source unit (e.g., CGS-ESU and CGS-EMU both rebasing
- Scalar conversion performance: 5–50x faster than v0.11.0 across all benchmarks
- Fast paths in
UnitProduct.__init__for single-factor and two-factor cases - Cached
UnitProduct.from_unit()results - Plain-Unit fast path in
Number.to()bypassing UnitProduct wrapping - Hash caching on
Vector,Dimension,Unit,UnitFactor - Dimension algebra caching (
__mul__,__truediv__,__pow__) Vectorcomponents useintinstead ofFractionfor common cases
- Fast paths in
- Removed
_Quantifiableand_nonefromucon.quantity.__all__
0.11.0 - 2026-03-28
ucon.basisis now a subpackage (ucon/basis/) with four modules:ucon.basis(__init__.py) — core types:Basis,BasisComponent,Vector,LossyProjection,NoTransformPathucon.basis.builtin— shipped basis instances:SI,CGS,CGS_ESU,NATURALucon.basis.transforms— transform types and instances:BasisTransform,ConstantBoundBasisTransform,ConstantBinding,SI_TO_CGS,CGS_TO_SI,SI_TO_CGS_ESU,SI_TO_NATURAL,NATURAL_TO_SIucon.basis.graph— registry and context scoping:BasisGraph,get_default_basis(),get_basis_graph(),using_basis(),using_basis_graph()- All symbols remain importable from
ucon.basisanduconvia re-exports
- Integration modules moved to
ucon.integrationssubpackage:ucon.numpy→ucon.integrations.numpyucon.pandas→ucon.integrations.pandasucon.polars→ucon.integrations.polarsucon.pydantic→ucon.integrations.pydantic
- Package discovery changed from explicit list to
setuptools.packages.find
ucon.basesmodule (contents split intoucon.basis.builtinanducon.basis.transforms)ucon.quantitymodule (backward-compatibility shim; importNumber,Ratiofromucon.coreorucon)
0.10.1 - 2026-03-25
- Affine conversion support in
EdgeDef(#215)EdgeDefgainsoffset: float = 0.0field for affine conversions (e.g., temperature scales)materialize()usesAffineMap(factor, offset)when offset is non-zero,LinearMap(factor)otherwiseload_package()reads optionaloffsetfield from TOML[[edges]]definitions- Backward compatible: existing packages and edge definitions are unaffected
- Domain-Specific Bases documentation (
docs/guides/domain-bases/)- Explains how to resolve SI dimensional degeneracies with extended bases
- Radiation Dosimetry: Gy vs Sv vs Gy(RBE) vs effective dose
- Pharmacology: drug potency, IU variations, morphine equivalents
- Clinical Chemistry: molarity vs osmolarity, Bq vs Hz, mEq vs mmol
- Classical Mechanics: torque vs energy, surface tension vs spring constant
- Thermodynamics: heat capacity vs entropy
0.10.0 - 2026-03-01
- NumPy array support via
NumberArrayclass- Vectorized arithmetic with unit tracking and uncertainty propagation
- Vectorized conversion:
heights.to(units.foot) - Reduction operations:
sum(),mean(),std(),min(),max() - Comparison operators returning boolean arrays for filtering
- N-D array support with broadcasting
- Callable syntax:
units.meter([1, 2, 3])returnsNumberArray
- Pandas integration via
NumberSeriesandUconSeriesAccessordf['height'].ucon.with_unit(units.meter).to(units.foot)- Arithmetic preserves unit semantics
- Polars integration via
NumberColumn- Wraps
pl.Serieswith unit metadata .to()conversion with unit tracking
- Wraps
- Map array support for vectorized operations
LinearMap,AffineMap,LogMap,ExpMapwork with numpy arrays_log()and_exp()helpers for scalar/array compatibility
- Optional dependencies:
ucon[numpy],ucon[pandas],ucon[polars] - Performance caching for repeated operations
- Conversion path caching in
ConversionGraph - Scale factor caching for scale-only conversions
- Unit multiplication/division caching
fold_scale()result caching onUnitProduct
- Conversion path caching in
- Performance benchmarks:
make benchmark,make benchmark-pint - Documentation:
docs/guides/numpy-arrays.md,docs/guides/pandas-integration.md,docs/guides/polars-integration.md - Example notebook:
examples/scientific_computing.ipynb
Unit.__call__andUnitProduct.__call__returnNumberArraywhen given list or ndarray input
0.9.4 - 2026-02-28
- MCP subpackage extracted to ucon-tools (#212)
- Install MCP server via
pip install ucon-tools[mcp] - Core ucon package no longer has MCP dependencies
- Namespace package support via
pkgutil.extend_path()enables coexistence
- Install MCP server via
ucon.mcpsubpackage (moved to ucon-tools)ucon-mcpCLI entry point (now in ucon-tools)mcpoptional dependency- MCP documentation (moved to ucon-tools, sourced via submodule)
0.9.3 - 2026-02-26
- Natural units support with
ConstantBoundBasisTransform(#206)NATURALbasis where c = ℏ = k_B = 1 (all quantities reduce to powers of energy)SI_TO_NATURALtransform maps SI dimensions to natural units (e.g., velocity → dimensionless)NATURAL_TO_SIinverse transform with constant bindings for reconstructionConstantBindingrecords relationships between dimensions via physical constantsLossyProjectionexception for dimensions without natural unit representation (e.g., current)allow_projection=Trueoption to drop unrepresentable dimensions
- Example demos for alternative unit systems (
examples/basis/)natural_units_demo.py— particle physics natural unitsgeometrized_units_demo.py— general relativity units (c = G = 1)elemental_units_demo.py— custom "elemental" basis
- Natural units guide in
docs/guides/natural-units.md - Natural units section in API reference
docs/reference/api.md - Custom constants documentation in README and API reference
- Comprehensive test suite for natural units (
tests/ucon/test_natural_units.py)
- MCP session state persistence across tool calls (#209)
- Custom units defined via
define_unit()are now resolvable in subsequentdefine_conversion()calls - Replaced
ContextVar-based isolation (per-task) with lifespan context (per-session) - Added
SessionStateprotocol andDefaultSessionStatefor injectable session management - All session-dependent tools now use FastMCP
Contextinjection
- Custom units defined via
- MCP session unit safety and visibility improvements (#209)
define_unit()now rejects duplicate unit names (prevents silent edge destruction)define_unit()now rejects alias collisions (prevents silent overwrites and cross-dimension corruption)check_dimensions()now sees session-defined unitslist_units()now includes session-defined unitscompute()correctly resolves session-defined units in denominators with numeric prefixes
0.9.2 - 2026-02-25
- MCP tools for physical constants (#204)
list_constants(category)lists available constants filtered by category- Categories:
"exact"(7),"derived"(3),"measured"(7),"session","all"
- Categories:
define_constant(symbol, name, value, unit, uncertainty, source)creates session constants- Session constants persist until
reset_session()is called
all_constants()function to enumerate built-in constantsget_constant_by_symbol()function for constant lookup by symbol or aliasConstant.categoryfield classifying constants as"exact","derived","measured", or"session"- MCP tools reference documentation for constants in
docs/reference/mcp-tools.md - Physical constants section in API reference
docs/reference/api.md
reset_session()now clears session constants in addition to custom units and conversions
0.9.1 - 2026-02-25
- pH unit with concentration dimension for mol/L ↔ pH conversions (#204)
(units.mole / units.liter)(1e-7).to(units.pH)returns<7.0 pH>units.pH(7.0).to(units.mole / units.liter)returns<1e-07 mol/L>- Follows established pattern: pH has concentration dimension (like dBm has POWER)
- Logarithmic units documentation in
docs/reference/units-and-dimensions.md examples/units/logarithmic.pydemonstration module
0.9.0 - 2026-02-25
Constantclass for physical constants with CODATA uncertainties- SI defining constants (exact):
c,h,e,k_B,N_A,K_cd,ΔνCs - Derived constants (exact):
ℏ(hbar),R(molar gas),σ(Stefan-Boltzmann) - Measured constants:
G,α,m_e,m_p,m_n,ε₀,μ₀ - Unicode aliases:
c,h,ℏ,k_B,N_A,G,α,ε₀,μ₀,mₑ,mₚ,mₙ - ASCII aliases:
hbar,alpha,epsilon_0,mu_0,m_e,m_p,m_n Constantarithmetic returnsNumberwith uncertainty propagationconstantsmodule exported fromucon
0.8.5 - 2026-02-25
parse()function for parsing human-readable quantity strings intoNumberobjects- Basic quantities:
parse("60 mi/h")returnsNumber(60, mile/hour) - Scientific notation:
parse("1.5e3 m")returnsNumber(1500, meter) - Uncertainty with
±:parse("1.234 ± 0.005 m")returnsNumberwith uncertainty - Uncertainty with
+/-:parse("1.234 +/- 0.005 m")(ASCII alternative) - Parenthetical uncertainty:
parse("1.234(5) m")means1.234 ± 0.005 - Pure numbers:
parse("100")returns dimensionlessNumber
- Basic quantities:
setup.py(redundant;pyproject.tomlis the single source of truth)
0.8.4 - 2026-02-25
using_basis()context manager for thread-safe scoped basis overrideusing_basis_graph()context manager for thread-safe scoped BasisGraph overrideget_default_basis()accessor returning context-local basis or SI fallbackget_basis_graph()accessor returning context-local or standard BasisGraphset_default_basis_graph()for module-level BasisGraph replacementreset_default_basis_graph()to restore standard BasisGraph on next accessDimension.from_components()andDimension.pseudo()now respect context basis
0.8.3 - 2026-02-25
- Auto-generated
dimension.pyistubs for IDE code completion (make stubs) Unit.is_compatible(other, basis_graph)method for checking conversion compatibilityUnit.basisproperty exposing the unit's dimensional basisconvert()validates dimensional compatibility viaBasisGraphConversionGraph.connect_systems()for bulk cross-basis edge registrationConversionGraph.list_rebased_units()for introspection- Dual-Graph Architecture documentation (
docs/architecture/dual-graph-architecture.md) - Cross-basis conversion guide in Custom Units & Graphs
- Basis System section in API reference
- CI: merge gate job, concurrency control, dependency caching, docs build check
- CI: CHANGELOG entry required for all PRs
- CHANGELOG with full version history
- Automated GitHub Releases from CHANGELOG on tag push
- Tests for Pydantic dimension constraints and constrained_number factory
RebasedUnitnow usesucon.basis.BasisTransform(unified implementation)- Removed old
BasisTransformfromucon/core.py - Removed
NewBasisTransformalias from exports - Updated ConversionGraph internals documentation with BasisGraph integration
- Updated API reference with cross-basis conversion methods
0.8.2 - 2026-02-24
- Basis-aware Dimensions (#193)
0.8.1 - 2026-02-24
- BasisGraph and standard bases (#191)
0.8.0 - 2026-02-23
- Basis abstraction (#189)
0.7.7 - 2026-02-23
- MCP server documentation (#188)
- Incompatible Python versions skip MCP tests (#186)
- MCP formula tools (#185)
- Architecture docs (#182)
0.7.6 - 2026-02-20
- Public-facing documentation site (#168)
- Getting Started guides (#175)
- Reference docs and styling (#180)
- Units extensions support for MCP (#167)
- Residual scale factor bug (#178)
0.7.5 - 2026-02-13
- True generic support for Pydantic integration (#166)
0.7.4 - 2026-02-12
- UnitPackage for unit organization (#164)
0.7.3 - 2026-02-12
- Graph-local name resolution (#163)
- MCP "compute" tool (#158)
0.7.2 - 2026-02-10
- Dimension.count for counting dimensions (#157)
0.7.1 - 2026-02-10
- MCP error infrastructure for multi-step chains (#154)
0.7.0 - 2026-02-09
- MCP error suggestions (#152)
0.6.11 - 2026-02-08
- Dimension enforcement decorator (#150)
0.6.10 - 2026-02-08
- Type-safe generics (#149)
0.6.9 - 2026-02-08
- Improved repr for derived Dimensions (#148)
0.6.8 - 2026-02-04
- Parser revamp (#144)
0.6.7 - 2026-02-04
- Factor structure normalization (#143)
0.6.6 - 2026-02-04
- New chemical engineering units (#142)
0.6.5 - 2026-02-04
- Same unit ratio information loss (#138)
0.6.4 - 2026-02-04
- Cubic centimeter (cc) support (#136)
0.6.3 - 2026-02-04
- Microgram (mcg) recognition (#134)
0.6.2 - 2026-02-04
- Milli-inch disambiguation (#133)
0.6.1 - 2026-02-03
- Nines notation support (#127)
0.6.0 - 2026-02-03
- MCP server (#121)
- Pydantic integration (#117)
- uv replaces nox for task running (#120)
0.5.2 - 2026-02-02
- Basis transforms and unit systems (#108, #109, #110, #111, #112, #113)
0.5.1 - 2026-02-01
- Uncertainty propagation (#106)
0.5.0 - 2026-02-01
- Dimensionless units (#104)
0.4.2 - 2026-01-30
- Number simplification (#95)
0.4.1 - 2026-01-30
- Information dimension (#96)
0.4.0 - 2026-01-30
- ConversionGraph and Maps (#90)
- Ergonomic unit conversion with
Number.to()(#92)
0.3.5 - 2026-01-27
- UnitFactor and UnitProduct exports from main package (#88)
- Unit and UnitProduct are now siblings (no inheritance) (#87)
- Unit combination refactor (#85)
- New license and copyright statements (#84)
0.3.4 - 2025-11-29
- Derived dimensions and composite units (#81)
- Scale multiplies Unit (#78)
- Unit provides Scale (#77)
- New package structure (#76)
- Dimension exponentiation (#73)
- Support for derived dimensions (#72)
- ucon.algebra module (#69)
0.3.3 - 2025-10-27
- Number refactor (#61)
0.3.2 - 2025-10-24
- Scale refactor (#54)
- Mermaid diagram for architecture (#49)
- CI simplification (#59, #57, #55)
0.3.1 - 2025-10-24
- Exponent refactor (#45)
- ROADMAP.md (#42)
- Formalized Python version support (#39)
- CI workflow sequencing (#37)
0.3.0 - 2025-10-18
- Dimension abstraction (#32)
- Coverage report badge (#30)
- File structure for multiple modules (#31)
- CI pipeline upgrades (#34)
0.2.2 - 2021-12-08
- Maintenance release
0.2.1 - 2020-09-07
- Maintenance release
0.2.0 - 2020-09-07
- Initial public API
0.1.1 - 2020-09-07
- Initial bug fixes
0.1.0 - 2020-09-07
- Initial alpha release
0.0.1 - 2020-09-05
- Project scaffolding
0.0.0 - 2020-08-08
- Initial commit