Latest Results
Add unxts.api, unxts.hypothesis, unxts.xarray namespace packages (#681)
* feat: add unxts.api namespace package (canonical unxts.api)
* feat: add unxts.hypothesis namespace package
* feat: add unxts.xarray namespace package; deprecate unxt-xarray
* โป๏ธ refactor(unxt-api): convert to backward-compat shim for unxts.api
* โป๏ธ refactor(unxt-hypothesis): convert to backward-compat shim for unxts.hypothesis
* ๐งน chore: add unxts.* packages to workspace; update coordinator tags and tools
* ๐ ci: add two-stage CD workflows for unxts.api, unxts.hypothesis, unxts.xarray
* ๐ ci: add test jobs and labels for unxts.api, unxts.hypothesis, unxts.xarray
* ๐ docs: add stub docs for unxts.api, unxts.hypothesis, unxts.xarray
* ๐งน chore: set v2.0.0 as first coordinated release; update shim version floors
* ๐ docs: update unxt-api and unxt-hypothesis READMEs with shim notice and migration guide
* ๐ docs: replace deprecated package docs with minimal redirect pages
* ๐ docs: strip deprecated package docs to minimal redirect pages
* ๐งน chore: remove unxt-xarray (never released; superseded by unxts.xarray)
* โ
test: replace shim test suites with re-export identity checks
* ๐จ style: fix import ordering flagged by pre-push lint hook
* โป๏ธ refactor: rename unxts.xarray to unxts.interop.xarray
Nests xarray integration under the unxts.interop namespace, another
open namespace for unxt integrations. Renames the package directory,
Python module path (unxts.interop.xarray, two levels of implicit PEP
420 namespace packages), git tag pattern (unxts-interop-xarray-v*),
CD/CD-publish/CD-PR workflows, coordinator tag wiring, CI test job,
labeler/labels entries, and docs. Also fixes a missed __version__
re-export in the unxt-hypothesis shim found while testing this change.
* ๐ docs: retarget deprecated-package doc symlinks to canonical unxts.* docs
docs/packages/{unxt-api,unxt-hypothesis} now symlink directly to the
canonical unxts.api/unxts.hypothesis docs (the packages that replaced
them) instead of to their own minimal redirect pages, which are now
removed as unreachable. Per-package docs for unxts.api,
unxts.hypothesis, and unxts.interop.xarray move into
packages/<name>/docs/, matching the existing pattern, with
docs/packages/<name> as a symlink to it.
Also: drop unxt-api and unxt-hypothesis from the "workspace" optional
dependency (unxt-api remains a core runtime dependency of unxt
regardless); add a new "interop-xarray" optional dependency for
unxts.interop.xarray and reference it from "workspace" via
unxt[interop-xarray] instead of repeating the package name.
* โจ feat: auto-import unxts.interop.xarray if installed
Registers the .unxt xarray accessor as a side effect of importing
unxt, mirroring the existing optional astropy/gala/matplotlib
interop hooks. No-op (and no hard dependency) if the optional
unxts.interop.xarray package isn't installed.
* โป๏ธ refactor: extract gala interop into unxts.interop.gala package
Moves the gala <-> unxt unitsystem conversion code from
src/unxt/_interop/unxt_interop_gala/ into a new canonical namespace
package, packages/unxts.interop.gala/ (src/unxts/interop/gala/,
mirroring the unxts.interop.xarray layout: implicit PEP 420 namespace
packages at both unxts and unxts.interop, git tag pattern
unxts-interop-gala-v*, two-stage CD/CD-publish/CD-PR workflows,
coordinator tag wiring, CI test job, labeler/labels entries, and docs
(packages/unxts.interop.gala/docs/ with a docs/packages/unxts.interop.gala
symlink).
unxt itself now imports unxts.interop.gala if installed (mirroring the
existing unxts.interop.xarray hook), registering the gala unitsystem
dispatches as a side effect -- no hard dependency on gala from unxt.
The "interop-gala" optional dependency now installs
unxts.interop.gala instead of gala directly; "workspace" references it
via unxt[interop-gala,interop-xarray] instead of spelling out the
package names.
* ๐ docs: move gala/matplotlib interop docs into their unxts.interop.* packages
docs/interop/gala.md and docs/interop/matplotlib.md move into
packages/unxts.interop.{gala,matplotlib}/docs/index.md respectively,
hooked into the main unxt docs the same way as unxts.interop.xarray --
via a docs/packages/<name> symlink and a "๐ฆ Packages" toctree entry --
instead of the generic "๐ค Interoperability" glob section.
โป๏ธ refactor: extract matplotlib interop into unxts.interop.matplotlib
Moves the matplotlib unit-converter registration out of
src/unxt/_interop/unxt_interop_mpl/ into a new canonical namespace
package, packages/unxts.interop.matplotlib/ (src/unxts/interop/matplotlib/),
mirroring the unxts.interop.{gala,xarray} layout: implicit PEP 420
namespace packages, git tag pattern unxts-interop-matplotlib-v*,
two-stage CD/CD-publish/CD-PR workflows, coordinator tag wiring, CI
test job, labeler/labels entries, and docs.
unxt now imports unxts.interop.matplotlib if installed (new
OptDeps.UNXTS_INTEROP_MATPLOTLIB), registering the matplotlib
converter as a side effect -- no hard dependency on matplotlib from
unxt. The "interop-mpl" optional dependency now installs
unxts.interop.matplotlib instead of matplotlib directly; "workspace"
references all three interop extras via
unxt[interop-gala,interop-mpl,interop-xarray].
* ๐ docs: document unxts.interop.gala's public convert_* functions
Documents that unxts.interop.gala exposes
convert_gala_unitsystem_to_unxt_unitsystem and
convert_unxt_unitsystem_to_gala_unitsystem as public API, while
recommending plum.convert(usys, ...) as the idiomatic way to invoke
them -- they're plum.conversion_methods registered as a side effect
of importing the package. Notes added to the package docstring, both
function docstrings, docs/index.md, and README.md.
* ๐ fix(cd): correct package_tag validation regex in unxts.* publish workflows
All five unxts.* stage-B publish workflows validated the release tag
against /^unxt-api-v.../ (a leftover from the cd-publish-unxt-api.yml
template) instead of their own tag pattern, so a valid
unxts-<pkg>-vX.Y.Z tag would be rejected and publishing would fail.
Each now matches its own pattern (unxts-api, unxts-hypothesis,
unxts-interop-gala, unxts-interop-matplotlib, unxts-interop-xarray).
* โ
test: add smoke-test suites for the unxts.* packages
The tests-unxts-* CI jobs run `pytest packages/unxts.<pkg>/tests`, but
those directories did not exist, so each job would exit with "file or
directory not found" when triggered. Add a minimal but functional
suite per package:
- unxts.api / unxts.hypothesis: public-API presence + a strategy check
- unxts.interop.gala: gala <-> unxt unitsystem conversion round-trip
- unxts.interop.matplotlib: converter registration + unit stripping
- unxts.interop.xarray: accessor registration + attach/strip round-trip
Also fix stale `unxt_xarray` references in the unxts.interop.xarray
docstrings (leftovers from the rename; the unxt-xarray package no
longer exists) to point at unxts.interop.xarray.
* ๐ ci: fix nox lint session for the unxts.* package layout
The `Format โ Lint` CI job (`nox -s lint`) failed for two reasons:
- `pylint(package='unxt')` could not resolve the `unxts.interop.*`
imports in `src/unxt/_interop/__init__.py`, because the pylint nox
session did not install those packages. The session now installs the
`workspace` extra so every namespace package is importable.
- `pylint(package='xarray')` (and the pytest xarray group) still
referenced `packages/unxt-xarray`, which was removed. The PackageEnum
is reworked to the current layout and each package is linted in its
own pylint process (so the duplicate-code checker does not flag the
intentional shim/canonical similarity), and pytest points the
namespace packages at their `tests/` dirs.
Also remove the dead `_src/` implementation trees left in the unxt-api
and unxt-hypothesis shims: the shims re-export from unxts.api /
unxts.hypothesis and never import `_src`, so those files were unused
duplicates (and the source of the pylint duplicate-code findings).
* ๐ ci: align unxts.* workflow action pins with main (post-rebase)
The unxts.* CD/CD-publish/CD-PR workflows were copied from the
unxt/unxt-api templates before the dependabot action bump (#680) that
now sits on main. After rebasing, bring every pinned action in the new
workflows (and the unxts.* test jobs in ci.yml) up to the same
versions main uses: actions/checkout v7.0.0, setup-uv v8.2.0,
codecov-action v7.0.0, build-and-inspect-python-package v2.18.0,
gh-action-pypi-publish v1.14.0, attest-build-provenance v4.1.1.
* โ
test: update validate_tag tests for the v2.0.0 threshold
scripts/validate_tag.py now grandfathers the whole 1.x series
(`major < 2`; v2.0.0 is the first coordinated release), but these
tests still used v1.11.x as their "new, strict-rules" version, so
six of them asserted rejection/coordinator-checks against tags that
are now legacy-valid. Bump the strict-path examples to the 2.x series
and add an explicit 1.11.0 legacy case documenting the new boundary.
* ๐ ci: fix full-suite jobs after unxt-hypothesis left the workspace extra
Removing unxt-hypothesis from the `workspace` optional dependency broke
the push/deps CI jobs (tests-unxt, test-oldest, test-newest,
test-interoperability): tests/unit/test_dims.py and test_quantity.py
imported `unxt_hypothesis`, which those jobs no longer install. Point
them at the canonical `unxts.hypothesis` (which the workspace extra
does install).
Also, the `workspace` extra now pulls matplotlib via
`unxt[interop-mpl]`, so the matplotlib integration tests are collected
in the nox `pytest(package='unxt')` session; switch that session to the
`test-all` group so `pytest-mpl` registers the `mpl_image_compare`
marker.
* ๐ ci: use importlib pytest import-mode to fix cross-package test collisions
The deps CI jobs (test-oldest, test-newest, test-interoperability) run a
bare pytest over the root testpaths, which collect both
packages/unxt-api/tests/test_public_api.py and
packages/unxt-hypothesis/tests/test_public_api.py. Under the default
prepend import-mode these same-named, __init__-less modules collide
("import file mismatch"). importlib mode imports each by its full path,
which also stops a namespace-package leaf dir (e.g. unxts/interop/xarray)
from shadowing the real library during src-doctest collection.
* ๐ fix: make gala interop inert where gala cannot build (Windows)
Putting interop-gala in the workspace extra meant the cross-platform
nox test env tried to install gala, which has no Windows wheels and
fails to build there (GSL / C++ compile errors), breaking the Windows
unxt CI jobs.
Mark gala as an off-Windows dependency of unxts.interop.gala
(`gala>=1.8; sys_platform != 'win32'`) so the package installs
everywhere but pulls gala only where it can build. unxt's interop hook
and the docs collection now gate on gala itself being importable (new
OptDeps.GALA), not just on the unxts.interop.gala distribution being
present, so `import unxt` stays safe and the gala docs are skipped on
Windows.
* ๐ ci: skip the deprecated unxt-hypothesis sweep and ignore gala docs on Windows
Two follow-ups so the push/deps CI jobs go green:
- Drop packages/unxt-hypothesis/ from the shared `testpaths`. It is a
deprecated shim that is no longer installed by default (removed from
the workspace extra), so the bare-pytest deps jobs (test-oldest,
test-newest, test-interoperability) hit ModuleNotFoundError importing
`unxt_hypothesis`. The shim keeps its own per-package CI job.
- Fix the gala/matplotlib docs collect-ignore to use the
docs/packages/<name> symlink path that pytest actually walks (the
previous packages/<name>/docs path never matched), so the gala doctest
page is correctly skipped on Windows where gala isn't installed.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> ๐ init: unxt-xarray (#498)
* ๐ init: unxt-xarray
* โจ feat(unxt-xarray): export functions as public API
* โจ feat(unxt-xarray): units attribute
* fix: accept dimensionless-Quantity integer indices in dynamic_slice/gather
JAX's nan-aware quantile (`nanquantile`/`nanmedian`) internally builds a
boolean nan-mask, performs integer arithmetic on it to obtain indices, then
calls `lax.dynamic_slice` and `lax.gather` with those indices. After #679
made comparisons return dimensionless `Quantity` objects, these index values
became dimensionless-Quantity integersโbut the two dispatch rules only
accepted `ArrayLike`, triggering a materialise error.
Broaden both rules to `ArrayLike | ABCQ` and strip the index with
`ustrip(AllowValue, โฆ)` before binding the primitive. The operand still
requires a typed `ABCQ` position, so no type piracy is introduced.
* feat(unxt-xarray): document and test unit propagation through xarray ops
Add an integration test suite (`test_operations.py`) covering reductions
(sum, prod, median, quantile), masking (where, fillna), and products (dot),
plus a test asserting that `unxt_xarray` leaves xarray's namespace resolver
untouched.
Expand the xarray-guide with an "Operations Preserve Units" section showing
which operations work natively and an honest Limitations section (dimension
coordinates, rolling, interp).
Wire `packages/unxt-xarray/` into the root `testpaths` so CI picks up the
package's tests.
* fix(unxt-xarray): address PR review comments
- conversion.py: copy attrs dicts defensively (dict(obj.attrs),
dict(coord.attrs)) to prevent mutations from propagating back to the
caller's objects
- conversion.py: use _array_attach_units() for coordinate data in
attach_units() to centralise unit-parsing logic
- docs/index.md: fix malformed nested code fence in uv tab-item
- .github/copilot-instructions.md: fix malformed tab-set example
* fix(ci): add unxt-xarray to the workspace optional-dependency
The "Newest supported deps" job and the nox pytest session with
`uv_extras=["workspace"]` install `--extra workspace` (via `--extra all`).
Since `unxt-xarray` was absent from that list, pytest collected its tests
but could not import the package, causing a collection error.
* fix(docs): remove stray fenced-code remnant in copilot-instructions tab-set example
* docs: flesh out the Documentation section in copilot-instructions
Replace the single tab-set bullet with real guidance: NumPy docstring
format, Sybil-as-doctest implications, example quality bar, and
MyST/Sphinx workflow.
* fix(unxt-xarray): address second round of PR review comments
strip_units: copy attrs dicts defensively (dict(coord.attrs),
dict(var.attrs), dict(obj.attrs)) to match the pattern already applied
to attach_units and prevent cross-object mutation.
docs: fix three examples that used dimension coordinates and claimed
the coordinate data would survive as a Quantity โ xarray coerces
dimension coords to plain arrays. Switch Coordinates-with-Units guide
example to a non-dimension coord; simplify the Quick Start examples in
docs/index.md and README.md to not show coordinate quantification.
Add missing `import unxt_xarray` and `import jax.numpy as jnp` to
xarray-guide.md code blocks so Sybil can execute them.
pyproject.toml: expand inline raw-options table into the structured
[tool.hatch.version.raw-options] / [tool.hatch.version.raw-options.scm.git]
form used by all other workspace packages.
* fix(unxt-xarray): address third round of PR review comments
- conversion.py attach_units(Dataset): copy var.attrs to dict for
consistency with coords and strip_units
- accessors.py: correct final_units annotation to include str since
callers may pass unit strings, not just AbstractUnit objects
- cd-unxt-xarray.yml: add paths: filter to pull_request trigger so the
workflow only runs on PRs that touch xarray package files
* docs(unxt-xarray): add lower-level API section, unit-conversion example, and dimension-coord warning
- Add prominent :::{warning} admonition near the Coordinates with Units section
so the dimension-coordinate pitfall is visible early, not just in Limitations
- Expand the Unit Conversion common-pattern example to show uconvert() on a
DataArray and the quantifyโconvertโdequantify roundtrip
- Add a new "Lower-Level API" section documenting attach_units, extract_units,
extract_unit_attributes, and strip_units with runnable examples and a
summary table of when to use each vs. the accessor
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Array-API-conformant boolean ops + dispatch consolidation (#679)
* feat: isfinite returns a dimensionless Quantity (Array API)
`isfinite` now returns a dimensionless Quantity of the same type as the
input, so the output shares a namespace with the input per the Array API,
rather than a raw jax Array.
To keep this coherent:
- Add mixed `and_p` registrations (Quantity/Array, both orders) that strip
the dimensionless operand and return a raw array, so JAX composites
(`isclose`, `allclose`, ...) that combine predicate results with raw
boolean arrays keep working.
- Teach `AbstractQuantity.__getitem__` to strip a dimensionless-Quantity
key, so boolean masking `q[isfinite(q)]` works.
* feat: comparisons (eq/ne/lt/le/gt/ge) return dimensionless Quantities
The comparison primitives (and `isnan`/`isinf`, which lower to `ne`/`eq`)
now return a dimensionless Quantity of the same namespace as the Quantity
operand, rather than a raw jax Array, for Array API conformance.
- Wrap all 18 comparison registrations via `_as_dimensionless_like`, which
preserves the operand's Quantity type but falls back to a plain
dimensionless `Quantity` for subtypes that cannot be dimensionless
(`Angle` -> ValueError, `StaticQuantity` -> TypeError on traced values).
- Add mixed `or_p` registrations (Quantity/Array, both orders) mirroring
the phase-1 `and_p` ones, so predicate/comparison results degrade to raw
arrays inside JAX composites (isclose, allclose, ...).
- Add `select_n` registrations for a dimensionless-Quantity selector over
raw-array / mixed cases (e.g. digitize, linalg.pinv).
- Update doctests, integration tests, and the quantity guide accordingly.
* test: lock in where driven by predicate/comparison conditions
`where` (select_n) already preserves the Quantity namespace; once
predicates and comparisons return dimensionless Quantities, feeding them
straight into `where` works end-to-end. Add an integration test guarding
that composition (comparison- and isfinite-driven conditions, plus
BareQuantity end-to-end). No production code needed โ the capability was
delivered by the comparison-phase select_n registrations.
* feat: logical ops + all return dimensionless Quantities
Complete the Array-API namespace consistency for boolean ops: logical/
bitwise AND/OR/XOR and `all` (reduce_and) now return a dimensionless
Quantity sharing the operand's namespace, matching the comparisons and
the already-converted OR/XOR/NOT/any.
- `and_p` (all variants incl. mixed Quantity/Array) -> dimensionless Q.
- `or_p`/`xor_p` mixed Quantity/Array variants -> dimensionless Q (xor
mixed registrations added).
- `reduce_and_p` (`jnp.all`) -> dimensionless Q; un-xfail test_all.
- Fix select_n with a dimensionless-Quantity selector to strip it and
re-dispatch through the array-selector registrations, so the result
follows the *cases'* namespace. This keeps `Angle % q` an Angle and
`StaticQuantity // StaticQuantity` a Quantity now that the internal
`where` conditions are Quantities rather than raw arrays.
- Update tests + doctests accordingly.
* refactor: consolidate sort_p dispatches into one variadic handler
Merge the separate one-operand (`sort`) and two-operand (`argsort`)
`sort_p` registrations into a single `*operands: ABCQ | ArrayLike`
dispatch that strips quantity operands, binds the primitive, and
re-wraps each output by position.
Besides removing duplication, this fixes a real gap: sorting a key
Quantity while reordering a *second* Quantity payload of a different
unit previously had no matching dispatch and raised "Refusing to
materialise". Each output now keeps its own unit. Add a doctest +
integration test covering multi-operand, different-unit sorting.
* refactor: consolidate dispatches without type piracy (concat/stack/sort/atan2)
concatenate_p / stack_p: collapse the three registrations each into a
shared `_combine_quantities` helper plus two thin registrations:
`(operand0: ABCQ, *rest)` and `(operand0: ArrayLike, operand1: ABCQ, *rest)`.
Every registration requires a quantity in a typed position, so quax never
routes an all-array `concatenate`/`stack` to these handlers -- no type
piracy. (The earlier `*operands: ABCQ | ArrayLike` variadic matched
all-array calls and wrapped them in a dimensionless Quantity; that band-aid
guard is gone.)
sort_p: tighten the consolidated handler the same way -- require the first
operand (a sort key) to be a quantity, so an all-array sort is not pirated.
atan2_p: drop three redundant registrations (6 -> 3). `atan2_p_qq`,
`atan2_p_vq`, and `atan2_p_qv` are strict subtypes of `atan2_p_aqaq`,
`atan2_p_vaq`, and `atan2_p_aqv` with identical results (`type_np` of a
parametric `Quantity` is `Quantity`), so the general registrations subsume
them. Their per-type doctests are folded into the survivors.
* test: guard against type piracy in concat/stack/sort
Add explicit regression tests asserting that concat, stack, sort, and
argsort of only raw arrays return a plain jax.Array and are never wrapped
in a Quantity. These lock in the "no type piracy" invariant so a future
change to the variadic registrations can't silently start matching
all-array calls.
* refactor: collapse the four quantity-selector select_n dispatches into one
Since a dimensionless-quantity `which` is handled by stripping it and
re-dispatching through `qlax.select_n`, the four quantity-selector
registrations (`select_n_p`, `_vq`, `_qjq`, `_qjj`) had byte-identical
bodies that differed only in their case-type signatures. Merge them into a
single `select_n_p_q(which: ABCQ, *cases: ABCQ | ArrayLike)`.
Requiring `which` to be a quantity keeps this from pirating an all-array
select_n; the result follows the cases' namespace (Quantity / Angle /
StaticQuantity, or a raw array when all cases are arrays), handled by the
array-selector registrations.
* refactor: remove redundant parametric trig dispatches (atan2-style)
The inverse-trig and (co)sine primitives each carried a parametric
`..._q(x: ABCPQ["dimensionless"]/["angle"])` registration that is a strict
subtype of the general `..._aq(x: ABCQ)` one with an identical result
(`type_np` of a parametric `Quantity` is `Quantity`). Drop the redundant
registrations for asin_p, asinh_p, atan_p, atanh_p, cos_p, and cosh_p
(folding their `u.Q` doctests into the survivors), and point
`cos_p_abstractangle` at `cos_p_aq`.
The `_abstractangle` variants are kept: they convert the Angle to a
Quantity first, since `type_np(Angle)(..., unit='')` would fail (an Angle
cannot be dimensionless).
Also drop the now-unused `Q` import.
* refactor: use Q alias instead of Quantity in register_primitives
* chore: import quaxed.lax as qlax in register_primitives
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Latest Branches
+20%
nstarman:tests/fix-benchmarks 0%
meeseeksmachine:auto-backport-of-pr-361-on-versions/v1.0.x ยฉ 2026 CodSpeed Technology