Latest Results
β¨ feat(linalg): astropy conversions for the matrix containers (#926)
* β¨ feat(linalg): astropy conversions for the matrix containers
`UnitsMatrix` and `astropy.units.StructuredUnit` are the same idea in two
libraries -- a nested layout of units -- and `QuantityMatrix` is that layout
with values attached, which astropy spells as a `Quantity` over a structured
dtype. Nothing converted between them here, so a downstream package
(coordinax) was carrying the conversions instead, in a module that imported
nothing of its own.
`astropy` is already a hard dependency of `unxt`, which `unxts.linalg`
requires, so these register unconditionally, the way `unxts.parametric`'s do.
The `QuantityMatrix -> Quantity` direction reads the layout rather than the
value's `ndim`: a nested layout over a (2, 2) value is one record of records,
a flat one over a (4, 3) value is four records, and the two are
indistinguishable by `ndim` alone. A value with more leading axes than
astropy's structured dtype can carry says so.
* π fix(linalg): a too-deep StructuredUnit named the wrong fault
Review: astropy nests to any depth, a `UnitsMatrix` is a vector or a matrix,
and anything deeper reached `UnitsMatrix`'s own check and came back as
"ragged structure" -- which is not what is wrong with it. Refuse by depth,
and say the depth.
An empty `StructuredUnit` keeps its existing message, which is already
accurate; it is skipped here because it has no first value to recurse into.
* π fix(linalg): an empty unit layout raised a bare StopIteration
Review, and reachable: `UnitsMatrix(np.empty(0, dtype=object))` is legal --
its own `__pow__` documents it -- so a `QuantityMatrix` can carry an empty
layout, and `_structured_depth` then had no first value to recurse into. The
conversion died on a message-less `StopIteration`.
Astropy has no counterpart to convert it to either: a structured dtype must
have at least one field. So this now says that, before computing a depth.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> β¨ feat(unitsystems): name the Planck and atomic base units (#875)
Closes #868.
`astropy.units` has no names for these, so they could only be spelled as a
scale times an SI unit -- and `to_string()` truncates past six significant
figures, so that spelling neither read well nor reparsed:
planck["length"] -> Unit("1.61626e-35 m") # and does *not* reparse
planck["length"] -> Unit("l_P") # now
Eight units: `l_P`, `m_P`, `t_P`, `T_P` for Planck, and `a_0`, `m_e`, `t_au`,
`e` for Hartree atomic. Only these two systems get names -- `hep` and
`geometrized` take a free scale, so their bases change with the parameter and
no fixed name would be right.
Each is defined from the *same constant expression* that already built its
unit system rather than a hard-coded number, so the values track whatever
CODATA revision `astropy.constants` ships. A test pins that.
Three things the naming had to dodge, each verified rather than assumed:
- `a0` and `Eh` are **not** free. astropy already parses them as
*dimensionless* and as a *time* (`E`Γ`h`, hour). Hence `a_0`, and no
Hartree-energy unit.
- A unit named `e` does not disturb scientific notation -- `1e5 m` and
`1.5e3 kg` still parse as a scale times a unit.
- astropy ignores a `format` string identical to the unit's own name and then
escapes the underscore, so `a_0` needed `a_{0}` to render as `$a_{0}$`
rather than `$\mathrm{a\_0}$`.
Registered with `add_enabled_units` at import, so `unxt.unit("l_P")` resolves
and the spelling round-trips. That mutates astropy's registry process-wide; it
is the reason this was kept out of #855, and the trade is deliberate. Upstream
issue to follow, which would let unxt drop the module entirely.
Doctests that hid these values behind `...` no longer need to: a name is
stable across CODATA revisions where six significant figures were not.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> π docs(dimensions): blank-line the lists in `dimension`'s docstring (#919)
Sphinx reported `ERROR: Unexpected indentation` against
`unxts.api._src.dimensions.dimension`, twice -- once per entry point
(`unxt/dims.py` and `unxt/__init__.py`). The docstring it names is plum's
*composed* one, so the reported line lands in the `str` method registered here
rather than in the abstract stub.
reStructuredText needs a blank line between a paragraph and a list that follows
it. Five lists in this docstring had none. Only the "Unsupported Operators" one
raised, because it is the only one with a wrapped continuation line for docutils
to trip over -- but the error was the smaller half of the problem. Without the
blank line docutils folds a list into the preceding paragraph, so *none* of the
six lists were being parsed: `publish_doctree` found 0 `bullet_list` and 0
`enumerated_list` nodes, and they rendered as run-together prose.
With the blank lines added it finds 5 and 1, and the built HTML carries real
`<ul>`/`<ol>` markup. A local `nox -s docs` is clean, with no remaining
`Unexpected indentation` anywhere in the build.
Note that this error did not fail the docs job. The one red `Docs` run on #917
was the external-link checker taking 504s from github.com on three unrelated
URLs, and it passed on re-run. This is a latent rendering bug, not a broken
build.
A repo-wide scan finds 18 further docstrings with the same paragraph-then-list
shape, none of which error but which likely mis-render the same way. Left for a
separate pass rather than widened into this one.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> π pin(deps): exclude plum-dispatch 2.10.0 (#917)
* π pin(deps): exclude plum-dispatch 2.10.0
plum 2.10.0 is the first release to ship mypyc-compiled wheels, and
`_function.py` is in the compile set. A mypyc *native* class gets neither
`__dict__` nor `__weakref__`, so `plum.Function` instances are no longer
weak-referenceable. `jax.jit` weakly references the callable it wraps
(`jax/_src/pjit.py:_cpp_pjit`), so every `jax.jit(<dispatched function>)`
now raises on first call:
TypeError: cannot create weak reference to 'Function' object
`unxt.uconvert` and `unxt.uconvert_value` are `plum.Function`s, so this is
user-facing, not merely a CI break. Reproduced by building v2.10.0 with
mypyc locally: 7 failures and 2 errors across
`tests/integration/test_uconvert_value_performance.py` and
`tests/benchmark/test_uconvert_value.py`, all of them `jax.jit` over a
dispatched function.
Only cp310-cp313 on manylinux, Windows and macOS x86_64 are affected --
plum publishes no cp314 wheel and no macOS arm64 wheel, so those platforms
fall back to `py3-none-any` and are fine. That is why the weekly
pre-release job failed on 3.12 and 3.13 but passed on 3.14, and why the
break does not reproduce on an arm64 Mac. The exclusion is unconditional
anyway: the resolver cannot know which wheel a given install will get.
`uv.lock` already pinned 2.9.0, so PR CI never saw this; only the weekly
`--upgrade` job did. The lock change here is metadata only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* π pin(deps): give `unxts.api` the 3.14 plum floor
Review follow-up.
`unxts.api` is the one package here with no `unxt` dependency, so nothing
supplies the root's `plum-dispatch>=2.9.0; python_version>='3.14'` floor
transitively. plum declares no upper `requires-python` bound on any release
(2.5.7 says `>=3.8`), so a standalone install of `unxts.api` on 3.14 could
resolve 2.5.7 -- below the version where plum gained 3.14 support. Spell the
floor out here; `uv pip compile --python-version 3.14` now picks 2.9.0.
`unxts.linalg` and `unxts.parametric` both depend on `unxt>=2.0.0` and inherit
the floor through it, the same way they already inherit its `>=2.7.0` over
their own `>=2.5.7`, so they are left alone.
Also sharpen the root comment: cp310-cp313 describes plum's wheel range, but
`unxt` requires >=3.12, so only 3.12 and 3.13 are actually reachable here. Link
the upstream fix as the trigger for removing the exclusion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Latest Branches
-11%
nstarman:claude/unit-systems-plum-d66f71 -19%
nstarman:quantity-parametric-rename +20%
nstarman:tests/fix-benchmarks Β© 2026 CodSpeed Technology