man advect/tutorials/scientific-python
SCIENTIFIC-PYTHON(1)User CommandsSCIENTIFIC-PYTHON(1)
────────────────────────────────────────────────────────────────────────────────────────

Arrays and Scientific Python

Advect works with the scientific Python stack in three complementary ways. NumPy and other Array API providers execute the numerical operations, advect.scipy supplies differentiable versions of selected SciPy functions, and advect.xarray preserves labels and coordinates around derivative results.

Write provider-neutral array code

Ask an input for its Array API namespace when the same function should run on more than one provider:

import numpy as np

import advect as ad


def centered_energy(x):
    xp = x.__array_namespace__()
    centered = x - xp.mean(x)
    return xp.sum(centered * centered)


x = np.asarray([1.0, 2.0, 4.0], dtype=np.float32)
gradient = ad.grad(centered_energy)(x)
print("NumPy gradient:", gradient)

The same function accepts another qualified provider without changing its body. For example, with array-api-strict installed:

import array_api_strict as xp

import advect as ad


def centered_energy(x):
    namespace = x.__array_namespace__()
    centered = x - namespace.mean(x)
    return namespace.sum(centered * centered)


x = xp.asarray([1.0, 2.0, 4.0], dtype=xp.float32)
print(ad.grad(centered_energy)(x))
# Array([-2.6666667, -0.6666667, 3.3333333], dtype=array_api_strict.float32)

With NumPy, write ordinary numpy.* calls; its own protocols route them through Advect. When a constructor contains traced values, give it a live dispatch anchor with like= or use ad.array or ad.asarray. CuPy follows the Array API path; its compatibility page describes the supported boundary.

Use the differentiable SciPy namespace

Install advect[scipy] and import functions from advect.scipy. Direct scipy.* calls are not intercepted. The special-function reference and SciPy compatibility table give the exact callable and lifetime contracts:

import numpy as np

import advect as ad
from advect.scipy.special import logsumexp


scores = np.array([1.0, 2.0, 4.0])
value, gradient = ad.value_and_grad(logsumexp)(scores)
print(f"logsumexp: {value:.6f}")
print("softmax gradient:", gradient)
# logsumexp: 4.169846
# softmax gradient: [0.04201007 0.1141952  0.84379473]

The optional namespace also includes differentiable image filters and solver callbacks used by implicit differentiation.

Preserve xarray labels

advect.xarray registers DataArray and Dataset as dynamic pytrees. Data buffers are differentiable; dimensions, coordinates, names, and attributes are static metadata restored on derivative results.

import numpy as np
import xarray as xr

import advect as ad
import advect.xarray  # register the container types


field = xr.DataArray(
    np.arange(6.0).reshape(2, 3),
    dims=("y", "x"),
    coords={"y": [10, 20], "x": [1, 2, 3]},
    name="field",
    attrs={"units": "V"},
)


def labeled_energy(x):
    centered = x - x.mean("x")
    return (centered * centered).sum(("x", "y"))


gradient = ad.grad(labeled_energy)(field)
print(gradient.values)
print("dims:", gradient.dims, "units:", gradient.attrs["units"])
# [[-2.  0.  2.]
#  [-2.  0.  2.]]
# dims: ('y', 'x') units: V

Floating and complex data variables are differentiable. Integer, boolean, string, and object buffers are rejected rather than assigned meaningless zero gradients. xarray owns alignment and named operations; Advect owns the array operations they lower to.

The xarray boundary is dynamic-only. Stage the raw array kernel, call it with field.data, and restore labels outside the program when reusable staging is needed.

$

[1:docs] [2:playground] $ man advect/tutorials/scientific-python