How to migrate to v2#

This guide covers the breaking changes introduced in unxt v2: the rename of the quantity classes and the extraction of the parametric quantity into the separate unxts.parametric package. If you are starting fresh with unxt, you do not need this guide — consult the Quantity reference and the parametric quantity guide for the current API.


Class rename mapping#

v1 name

v2 name

Short alias

Notes

BareQuantity

Quantity

u.Q

Now the default, non-parametric class

Quantity (parametric)

ParametricQuantity

up.PQ

Opt-in; moved to the unxts.parametric package

Q

u.Q

New alias for Quantity

PQ

up.PQ

New alias for ParametricQuantity (in unxts.parametric)

Note

As of v2, ParametricQuantity/PQ live in the separate unxts.parametric package rather than in unxt. Install it with pip install unxts.parametric and import as import unxts.parametric as up (so up.PQ). Accessing unxt.ParametricQuantity / u.PQ now raises an AttributeError pointing here.


Package Split: ParametricQuantity Moved to unxts.parametric#

In v2 the parametric quantity classes live in a separate package, unxts.parametric, rather than in unxt. Core unxt no longer imports or depends on ParametricQuantity at all. Install the package to opt in:

pip install unxts.parametric   # or: uv add unxts.parametric

Accessing the moved names on unxt now raises AttributeError with a message pointing to the new package — this covers unxt.ParametricQuantity, unxt.PQ, unxt.AbstractParametricQuantity, and their unxt.quantity.* equivalents.

The two support packages were likewise renamed into the shared unxts.* namespace: unxt-apiunxts.api and unxt-hypothesisunxts.hypothesis (imported as unxts.api / unxts.hypothesis). The old distributions and their import unxt_api / import unxt_hypothesis names still work as compatibility shims, but prefer the unxts.* names.

Update your imports#

v1 (unxt)

v2 (unxts.parametric)

from unxt import ParametricQuantity

from unxts.parametric import ParametricQuantity

from unxt import PQ

from unxts.parametric import PQ

from unxt.quantity import AbstractParametricQuantity

from unxts.parametric import AbstractParametricQuantity

u.PQ(...) / u.ParametricQuantity(...)

up.PQ(...) (with import unxts.parametric as up)

The v1 form (shown for reference — this raises AttributeError on v2):

import unxt as u

q = u.PQ(1, "m")

The v2 form:

>>> import unxts.parametric as up

>>> up.PQ(1, "m")
ParametricQuantity(Array(1, dtype=int32...), unit='m')

Angle operations now return the default Quantity#

Trigonometric and product operations on an Angle (cos, sin, tan, cbrt, Angle @ Angle, Angle * Angle, integer/array powers, etc.) previously produced a ParametricQuantity. Because core unxt can no longer reference the parametric class, in v2 they produce the lightweight default Quantity:

>>> import unxt as u
>>> import quaxed.numpy as jnp

>>> jnp.cos(u.Angle(0, "deg"))  # v1: ParametricQuantity
Quantity(Array(1., dtype=float32...), unit='')

>>> u.Angle([1, 2, 3], "deg") @ u.Angle([4, 5, 6], "deg")  # v1: ParametricQuantity
Quantity(Array(32, dtype=int32), unit='deg2')

The value and unit are unchanged — only the wrapping class differs. If you specifically need a parametric result, convert explicitly with convert(result, up.PQ).

Parametric operands need unxts.parametric imported#

A few JAX primitive rules fire only when a parametric quantity is involved — raising a quantity to a dimensionless ParametricQuantity exponent, % (remainder), and clamp with parametric bounds. These rules are registered as an import side effect of unxts.parametric. Importing the package (which you do to use up.PQ at all) registers them; if a ParametricQuantity reaches your code some other way, import unxts.parametric once at startup.

Astropy conversion#

Converting an astropy.units.Quantity to a ParametricQuantity is now registered by unxts.parametric (import it to enable). Conversion to the default Quantity remains in core unxt:

>>> from astropy.units import Quantity as AstropyQuantity
>>> from plum import convert
>>> import unxt as u
>>> import unxts.parametric as up

>>> convert(AstropyQuantity(1.0, "cm"), u.Quantity)  # core unxt
Quantity(Array(1., dtype=float32), unit='cm')

>>> convert(AstropyQuantity(1.0, "cm"), up.PQ)  # needs unxts.parametric
ParametricQuantity(Array(1., dtype=float32), unit='cm')

Config: include_params moved to unxts.parametric.config#

The include_params display option — whether repr()/str() show the ['length']-style dimension parameter — only affects parametric quantities, so it moved out of unxt.config into unxts.parametric.config. unxt.config now rejects it as an unknown option.

v1 (unxt.config)

v2 (unxts.parametric.config)

u.config.quantity_repr.include_params

up.config.quantity_repr.include_params

u.config.override(quantity_repr__include_params=True)

up.config.override(quantity_repr__include_params=True)

[tool.unxt.quantity.repr]include_params

[tool.unxts.parametric.quantity.repr]include_params

Defaults are unchanged (repr hides the parameter, str shows it). The other display settings (short_arrays, use_short_name, named_unit, indent) remain in unxt.config. See the parametric quantity guide.

Config file section renamed to [tool.unxts.unxt]#

unxt’s pyproject.toml display configuration moved from [tool.unxt...] to [tool.unxts.unxt...], matching the shared unxts.* namespace (alongside [tool.unxts.parametric...]). Rename any existing sections:

v1

v2

[tool.unxt.quantity.repr]

[tool.unxts.unxt.quantity.repr]

[tool.unxt.quantity.str]

[tool.unxts.unxt.quantity.str]

In-code configuration via u.config is unchanged.

If you do not rename, the leftover [tool.unxt.*] section is silently ignored and those settings revert to their defaults. unxt emits a DeprecationWarning at import so the drift is greppable:

The ‘[tool.unxt]’ pyproject.toml section is deprecated and ignored; unxt now reads its configuration from ‘[tool.unxts.unxt]’. Move your settings there.


Deprecation: BareQuantity#

BareQuantity is now a deprecated alias of Quantity. Accessing it emits a DeprecationWarning and returns the (new) Quantity class:

>>> import warnings
>>> import unxt as u

>>> with warnings.catch_warnings(record=True) as w:
...     warnings.simplefilter("always")
...     BQ = u.quantity.BareQuantity  # DeprecationWarning
...     assert BQ is u.Quantity  # same class

>>> w[0].category.__name__
'DeprecationWarning'

BareQuantity will be removed in a future release. Update your code now:

The v1 form (deprecated; still works, but warns):

from unxt import BareQuantity

q = BareQuantity(1.0, "m")

The v2 form:

>>> from unxt import Quantity  # or: import unxt as u; u.Q(...)

>>> Quantity(1.0, "m")
Quantity(Array(1., dtype=float32...), unit='m')

Behavioral Changes for Former Quantity Users#

If you used the old parametric Quantity, two behaviors have changed:

(a) Subscripting no longer dimension-checks by default#

In v1, Quantity["length"](1, "s") would raise a ValueError because "s" (seconds) is not a length unit. In v2, the same call on the new default Quantity accepts any unit without checking:

On the v2 default Quantity the subscript is a no-op, so no check happens:

>>> import unxt as u
>>> import unxts.parametric as up

>>> u.Q["length"](1, "s")
Quantity(Array(1, dtype=int32...), unit='s')

ParametricQuantity still raises on a mismatch:

>>> try:
...     up.PQ["length"](1, "s")
... except ValueError as e:
...     print(e)
Physical type mismatch.

Migration: Replace Quantity["<dim>"](...) calls that relied on dimension checking with ParametricQuantity["<dim>"](...) (or up.PQ["<dim>"](...)).

(a2) isinstance checks against Quantity no longer match parametric quantities#

Warning

This is the one v1 break that gives you neither a warning nor an error — the check simply returns False and your code takes the other branch.

In v1, u.Quantity was the parametric class, so isinstance(x, u.Quantity) matched parametric instances. In v2 they are unrelated concrete classes, and neither is a subclass of the other:

>>> import unxt as u
>>> import unxts.parametric as up

>>> pq = up.PQ(1.0, "m")
>>> q = u.Q(1.0, "m")

>>> isinstance(pq, u.Quantity)   # v1: True
False

>>> isinstance(q, up.PQ)
False

Migration: check against the abstract base, which both concrete classes share:

>>> isinstance(pq, u.AbstractQuantity)
True

>>> isinstance(q, u.AbstractQuantity)
True

The same applies to issubclass, to plum/beartype annotations written as Quantity, and to any runtime validation (pydantic, attrs validators, manual type guards) that narrows on u.Quantity. If you want “any quantity”, use u.AbstractQuantity (or u.quantity.is_any_quantity); reserve u.Quantity for “specifically the non-parametric class”.

(b) Plum dispatch on dimension-specific types requires ParametricQuantity#

In v1, Quantity["length"] was a distinct class usable in plum dispatch annotations. In v2, u.Q["length"] is u.Quantity — subscripting the default Quantity returns the same class, making it useless for dimension-based dispatch.

So an annotation written this way no longer selects on dimension:

@dispatch
def f(x: Quantity["length"]):
    ...  # was a distinct type in v1

Use ParametricQuantity for dimension-specific dispatch instead:

>>> from plum import dispatch
>>> import unxts.parametric as up

>>> @dispatch
... def f(x: up.PQ["length"]):
...     return "length!"

>>> @dispatch
... def f(x: up.PQ["time"]):
...     return "time!"

>>> f(up.PQ(1.0, "m"))
'length!'

>>> f(up.PQ(1.0, "s"))
'time!'

(c) Equality on StaticValue-backed quantities is now unit-blind#

A Quantity whose value is wrapped in StaticValue (so it can be a jax.jit static_argnames key) now compares with == structurally — same unit label and array — rather than converting units first. Like the isinstance change, this is silent: no warning, == just returns a different answer.

>>> import unxt as u
>>> from unxt.quantity import StaticValue

>>> a = u.Q(StaticValue(1000.0), "m")
>>> b = u.Q(StaticValue(1.0), "km")

>>> a == b   # v1: True (unit-aware); v2: unit-blind
False

Migration: for a unit-aware “same physical quantity” check, use u.equivalent (or the .is_equivalent method) as the drop-in replacement for v1 ==:

>>> u.equivalent(a, b)
True

>>> a.is_equivalent(b)
True

Why the default changed#

The motivation — and, more usefully, the reason it is not about jax.jit cache misses — is set out in Why Quantity is not parametric. The short version: both classes produce the same number of jit compilations, and what the parametric class actually costs is a Python class and a pytree node type per dimension.


Note on Pickles#

Old pickles that reference the private module path unxt._src.quantity.quantity.Quantity resolve to the new Quantity class (i.e. the former BareQuantity). If you have pickles that stored instances of the old parametric Quantity (now ParametricQuantity), they will deserialize as the wrong type. Re-generate those pickles after upgrading.