Skip to content

API reference

Every function in fastfields.dlpack, with its arguments and options.

fastfields.dlpack

fastfields.dlpack: nanobind DLPack bindings to the fastfields-lib C++ lib.

All functions accept any array object exposing __dlpack__ (numpy, torch, cupy, ...). Tensors marked in-place / as outputs are written through their DLPack data pointers.

Argument checking

This is the raw layer and it stays sharp: it does not reshape, cast, allocate or copy anything for you. It does reject structurally malformed arrays -- a null data pointer, a 0-d array, a vector (multi-lane) dtype, arrays of a single call spread across different devices -- with a ValueError naming the function and the argument, instead of passing them on to the kernels. Shape and dtype contracts (matching ranks, matching batch shapes, ndim vs. rank) are enforced one layer down, by fastfields-lib, and also surface as ValueError.

CUDA lifetime invariant

Every array passed to a CUDA op must be kept alive by the caller until the stream it was submitted on has been synchronized.

These bindings are asynchronous on CUDA: a call enqueues work on stream and returns immediately, and nothing in this package holds a reference to your arrays -- the DLPack tensors handed to C++ are plain views over memory the Python objects own.

If an input, output or temporary is dropped before the stream is synchronized, its memory goes back to the framework's allocator (CuPy's memory pool, PyTorch's caching allocator) while a kernel may still be reading or writing it. The allocator can then hand that same block to an unrelated allocation: silent corruption, not a crash. The frameworks differ in when such reuse is possible -- PyTorch's caching allocator records the stream a block was used on, CuPy's pool does not -- so code that happens to work under one is not portable to the other.

In practice:

.. code-block:: python

out = cupy.empty_like(inp)
ff.pull(out, inp, grid, stream=stream.ptr)
stream.synchronize()        # only now may inp / grid / out be dropped
del inp, grid               # (or let them go out of scope)

The friendly wrappers satisfy this for you, which is why it is easy to forget: they submit on the framework's own current stream (fastfields.torch via _util.stream_ptr, fastfields.cupy via _util.current_stream_ptr), so the framework's ordering rules cover the memory they allocated, and they keep the operands referenced for the whole call -- fastfields.torch beyond it, through autograd's save_for_backward. Calling fastfields.dlpack directly, or passing a stream of your own, means taking the invariant on yourself. On CPU it holds trivially -- the calls are synchronous.

Bound

Bases: IntEnum

Boundary condition (passed as the bound argument).

Spline

Bases: IntEnum

Spline interpolation order (passed as the spline argument).

anchor_scale_shift

anchor_scale_shift(anchor: str, inshape: Sequence[int], outshape: Sequence[int], ndim: int) -> tuple[list[float], float]

Map a torch-interpol anchor to a per-dim scale and scalar shift.

The fastfields resize kernel samples input coordinate scale[d] * loc + shift * (scale[d] - 1) for output index loc. The four anchors of interpol.resize map onto (scale, shift) as:

========== ================= ======= anchor scale[d] shift ========== ================= ======= centers (in-1)/(out-1) 0.0 edges in/out 0.5 first in/out 0.0 last in/out 1.0 ========== ================= =======

Parameters:

Name Type Description Default
anchor str

Anchor name or abbreviation (centers/edges/first/last or c/e/f/l); matched case-insensitively on the first letter.

required
inshape sequence of int

Input and output spatial sizes (length ndim).

required
outshape sequence of int

Input and output spatial sizes (length ndim).

required
ndim int

Number of spatial dimensions.

required

Returns:

Name Type Description
scale list of float

Per-dim input-index step per output-index step.

shift float

Scalar sampling shift shared across dimensions.

Raises:

Type Description
ValueError

If anchor is empty or its first letter is not one of c/e/f/l.

as_bound

as_bound(value: int | str | Bound) -> int

Normalise a boundary-condition argument to an int in 0..7.

Accepts an integer, a :class:Bound enum, or a friendly string alias (e.g. "dct2", "wrap"). Raises ValueError for an unknown alias or an out-of-range integer.

as_spline

as_spline(value: int | str | Spline) -> int

Normalise a spline-order argument to an int in 0..7.

Accepts an integer, a :class:Spline enum, or a friendly string alias (e.g. "cubic"). Raises ValueError for an unknown alias or an out-of-range integer.

check_ndim

check_ndim(ndim: int, arr_ndim: int) -> None

Validate that ndim is in 1..arr_ndim (else ValueError).

infer_ndim

infer_ndim(ndim: Optional[int], factor: float | Sequence[float] | None, shape: int | Sequence[int] | None) -> int

Infer the number of trailing spatial dimensions to resize.

An explicit ndim wins; otherwise a sequence shape or factor implies its length; failing that, 1.

normalize_shape

normalize_shape(shape: int | Sequence[int], ndim: int) -> list[int]

Normalise a shape argument to a list of int of length ndim.

A scalar int is broadcast to ndim entries. Raises ValueError if an explicit sequence does not have length ndim.

resolve_out_spatial

resolve_out_spatial(spatial_in: Sequence[int], ndim: int, factor: float | Sequence[float] | None, shape: int | Sequence[int] | None) -> tuple[int, ...]

Resolve the output spatial shape from factor or shape.

factor and shape are mutually exclusive; with neither, the output keeps the input spatial shape (identity). A scalar factor is broadcast to ndim entries; each output size is max(1, round(in * factor)).