Skip to content

Use a Quax type¤

The other tutorials teach you to write an array-ish type. This one is for using one that already exists.

You will take a function that knows nothing about physical units and run it on arrays that carry them — without editing the function. By the end you will have caught a units bug inside code you did not write, and differentiated through it.

1. A function that knows nothing about you¤

import jax.numpy as jnp


def kinetic_energy(mass, velocity):
    return 0.5 * mass * velocity**2

Ordinary JAX. It multiplies and it squares; it has no opinion about units.

2. Give it values that do¤

quax.examples.unitful ships an array-ish type that carries units. Build two values and wrap the function in quax.quaxify:

import quax
from quax.examples.unitful import kilograms, meters, seconds, Unitful

mass = Unitful(jnp.asarray(2.0), kilograms)
velocity = Unitful(jnp.asarray(3.0), {meters: 1, seconds: -1})

energy = quax.quaxify(kinetic_energy)(mass, velocity)
print(energy.array, energy.units)  # 9.0 {kg: 1, m: 2, s: -2}

Joules, derived rather than declared. quaxify reinterpreted each multiply and each power according to what you passed in, the way jax.vmap reinterprets them as their batched versions.

3. Get a unit error out of code that cannot raise one¤

Now the part a plain array cannot do for you. Add an energy to a velocity:

try:
    quax.quaxify(lambda e, v: e + v)(energy, velocity)
except ValueError as e:
    print(e)  # Cannot add two arrays with units {kg: 1, m: 2, s: -2} and {m: 1, s: -1}.

Nothing in the lambda checks anything. The rule registered for add on Unitful raised, at trace time, before a single number was added.

4. Compose with the rest of JAX¤

quaxify is a JAX transform, so the others still work — in either order, and nested:

import jax

fast = jax.jit(quax.quaxify(kinetic_energy))
print(fast(mass, velocity).units)  # {kg: 1, m: 2, s: -2}

grad = jax.grad(lambda v: quax.quaxify(kinetic_energy)(mass, v).array)(velocity)
print(type(grad).__name__, grad.units)  # Unitful {m: 1, s: -1}

The gradient came back as a Unitful rather than a bare array, so your type survives the backward pass. Its units are the velocity's, which is not the same thing as the derivative's — Autodiff covers why.

What you did¤

You ran unmodified JAX code on a custom type, had that type reject an operation that made no physical sense, and differentiated the result. That is the whole user-facing surface: build values, wrap the function, read the result.

Where to go next:

  • Sharp bits — where a type stops surviving, and why.
  • How-to guides — for when you hit a specific problem.
  • Custom rules — if you now want to write a type of your own.