# Mesh Alignment

> NumKong for C

### Many-to-Many Shape Distances

Shape kernels compare 3D point clouds of any size, with no correspondence between points: Chamfer, Hausdorff, Wasserstein-Procrustes, and Gromov-Wasserstein.
Each cloud is packed once, centered and rotated onto its principal axes, and every kernel scores a list of row packs against a list of column packs.
Packs are built one cloud at a time, so a caller passes whatever packs it holds as an array of pointers.

```c
nk_f32_t const query[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};          // 4 points
nk_f32_t const candidate[] = {0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 1, 1}; // 5 points
nk_size_t const alignment = nk_default_alignment_k; // `aligned_alloc` sizes are whole multiples of it
nk_size_t query_bytes = 0, candidate_bytes = 0;
nk_shapes_pack_size_f32_best(4, capabilities, &query_bytes);
nk_shapes_pack_size_f32_best(5, capabilities, &candidate_bytes);
void *query_packed = aligned_alloc(alignment, (query_bytes + alignment - 1) / alignment * alignment);
void *candidate_packed = aligned_alloc(alignment, (candidate_bytes + alignment - 1) / alignment * alignment);
nk_shapes_pack_f32_best(query, 4, query_packed, capabilities, NULL);
nk_shapes_pack_f32_best(candidate, 5, candidate_packed, capabilities, NULL);

void const *rows[] = {query_packed}, *columns[] = {candidate_packed};
nk_f64_t chamfer = 0, procrustes = 0;
nk_f32_t rotation[9]; // takes the candidate onto the query, through the centroids in the headers
nk_size_t chamfer_bytes = 0;
nk_chamfers_workspace_size_f32_best(4, 5, capabilities, &chamfer_bytes);
void *chamfer_workspace = aligned_alloc(alignment, (chamfer_bytes + alignment - 1) / alignment * alignment);
nk_chamfers_packed_f32_best(rows, 1, columns, 1, &chamfer, sizeof(nk_f64_t), rotation, INFINITY, chamfer_workspace,
                            chamfer_bytes, capabilities, NULL);

nk_size_t workspace_bytes = 0;
nk_wassersteinprocrustes_workspace_size_f32_best(4, 5, capabilities, &workspace_bytes);
void *workspace = aligned_alloc(alignment, (workspace_bytes + alignment - 1) / alignment * alignment);
nk_wassersteinprocrustes_packed_f32_best(rows, 1, columns, 1, &procrustes, sizeof(nk_f64_t), NULL, workspace,
                                         workspace_bytes, capabilities, NULL);

nk_f64_t query_radius = 0, candidate_radius = 0; // |query_radius - candidate_radius| bounds Hausdorff from below
nk_shapes_packed_shape_f32_best(query_packed, NULL, NULL, NULL, NULL, NULL, &query_radius, capabilities, NULL);
nk_shapes_packed_shape_f32_best(candidate_packed, NULL, NULL, NULL, NULL, NULL, &candidate_radius, capabilities, NULL);
nk_f32_t restored[15]; // the candidate again, within one rounding into `f32`
nk_shapes_unpack_f32_best(candidate_packed, restored, capabilities, NULL);
```

A pack is opaque: `nk_shapes_packed_shape_*` reads its points, centroid, principal frame as a quaternion, scale, descending spreads, and largest radius, and `nk_shapes_unpack_*` restores its cloud.
The spreads and radius bound Wasserstein-Procrustes and Hausdorff from below for free, the way vector norms bound dot products.
Gromov-Wasserstein and Wasserstein-Procrustes take a caller's workspace, since a transport plan depends on both clouds and fits in neither pack.

`nk_shapes_pack_normalized_*` takes the same arguments and also divides each cloud by its RMS distance from the centroid, which it keeps as the scale, so every kind ignores size as well as pose.
Each kind compares the packed coordinates, so mixing absolute and normalized packs in one call is the caller's choice.
Between normalized packs, a rotation takes column cloud `b` onto row cloud `a` as aᵢ ≈ (scale_a / scale_b) × R × (bⱼ − b̄) + ā.

In C++, `nk::shapes_packed` owns many packs in one allocation and exposes the pointer array, and `make_normalized` packs them normalized:

```cpp
nk::f32_t const query[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
nk::f32_t const candidate[] = {0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 1, 1};
std::span<nk::f32_t const> clouds[] = {query, candidate};
auto [packs, status] = nk::shapes_packed<nk::f32_t>::make(clouds);
nk::f64_t distances[4];
status = nk::hausdorffs_packed<nk::f32_t>(packs.data(), 2, packs.data(), 2, distances, 2 * sizeof(nk::f64_t), nullptr,
                                          nk::f64_t::positive_infinity());
```

`#include <numkong/capabilities.h>`

`#include <numkong/shapes.h>`

## nk_shapes_workspace_size_punned_t

Workspace sizes are host arithmetic, like pack sizes, so they take no stream.

```c
typedef nk_status_t(*) nk_shapes_workspace_size_punned_t(nk_size_t row_points, nk_size_t column_points, nk_size_t *bytes);
```

## nk_shapes_pack_size_*_best

Returns the bytes a pack of `points` takes.

```c
nk_status_t nk_shapes_pack_size_f64_best(
    nk_size_t points,
    nk_capability_t capabilities,
    nk_size_t *bytes);
```

- `nk_shapes_pack_size_f64_best`
- `nk_shapes_pack_size_f32_best`
- `nk_shapes_pack_size_f16_best`

- `nk_shapes_pack_size_f64` backends: serial
- `nk_shapes_pack_size_f32` backends: serial
- `nk_shapes_pack_size_f16` backends: serial

- `points` (in): The number of 3D points.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `bytes` (out): The size of the pack.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_shapes_pack_*_best

Packs one cloud: centers it, rotates it onto its principal axes, and keeps its constants.

```c
nk_status_t nk_shapes_pack_f64_best(
    nk_f64_t const *points,
    nk_size_t count,
    void *packed,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_shapes_pack_f64_best`: points `nk_f64_t`
- `nk_shapes_pack_f32_best`: points `nk_f32_t`
- `nk_shapes_pack_f16_best`: points `nk_f16_t`

- `nk_shapes_pack_f64` backends: serial
- `nk_shapes_pack_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv
- `nk_shapes_pack_f16` backends: serial, haswell, skylake, v128relaxed, neon, rvv

- `points` (in): The cloud, `count` × 3 interleaved [x0,y0,z0, x1,y1,z1, ...].
- `count` (in): The number of 3D points.
- `packed` (out): The pack, as many bytes as [`nk_shapes_pack_size_f64_best`](#nk_shapes_pack_size_f64_best) reports, starting on an `nk_default_alignment_k` boundary.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_shapes_pack_normalized_*_best

Packs one cloud like [`nk_shapes_pack_f64_best`](#nk_shapes_pack_f64_best), also dividing it by its RMS distance from the centroid, kept as the pack's `scale`, or by 1 when the cloud has no spread.

```c
nk_status_t nk_shapes_pack_normalized_f64_best(
    nk_f64_t const *points,
    nk_size_t count,
    void *packed,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_shapes_pack_normalized_f64_best`: points `nk_f64_t`
- `nk_shapes_pack_normalized_f32_best`: points `nk_f32_t`
- `nk_shapes_pack_normalized_f16_best`: points `nk_f16_t`

- `nk_shapes_pack_normalized_f64` backends: serial
- `nk_shapes_pack_normalized_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv
- `nk_shapes_pack_normalized_f16` backends: serial, haswell, skylake, v128relaxed, neon, rvv

Every kind then compares shapes regardless of size, and a rotation between two normalized packs takes the column cloud onto the row cloud as aᵢ ≈ (scale_a / scale_b) × R × (bⱼ − b̄) + ā.

## nk_shapes_packed_shape_*_best

Reads a pack's points and constants, skipping every null output.

```c
nk_status_t nk_shapes_packed_shape_f64_best(
    void const *packed,
    nk_size_t *points,
    nk_f64_t *centroid,
    nk_f64_t *frame,
    nk_f64_t *scale,
    nk_f64_t *spreads,
    nk_f64_t *max_radius,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_shapes_packed_shape_f64_best`: centroid, frame, scale, spreads, max_radius `nk_f64_t`
- `nk_shapes_packed_shape_f32_best`: centroid, frame, scale, spreads, max_radius `nk_f64_t`
- `nk_shapes_packed_shape_f16_best`: centroid, frame, scale, spreads, max_radius `nk_f32_t`

- `nk_shapes_packed_shape_f64` backends: serial
- `nk_shapes_packed_shape_f32` backends: serial
- `nk_shapes_packed_shape_f16` backends: serial

- `packed` (in): A pack of `f64` points, plain or normalized.
- `points` (out): Null, or the number of 3D points.
- `centroid` (out): Null, or the 3 coordinates of the mean input point.
- `frame` (out): Null, or the unit quaternion (x, y, z, w) of 𝑄, rotating centered inputs onto the principal axes.
- `scale` (out): Null, or what the rotated inputs were divided by: their RMS distance from the centroid, or 1.
- `spreads` (out): Null, or the 3 descending eigenvalues of the packed points' second moments.
- `max_radius` (out): Null, or the largest packed point norm.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_shapes_unpack_*_best

Restores a pack's cloud as x = scale × 𝑄ᵀ × x′ + centroid, computed in the math type and rounded once into the input type.

```c
nk_status_t nk_shapes_unpack_f64_best(
    void const *packed,
    nk_f64_t *points,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_shapes_unpack_f64_best`: points `nk_f64_t`
- `nk_shapes_unpack_f32_best`: points `nk_f32_t`
- `nk_shapes_unpack_f16_best`: points `nk_f16_t`

- `nk_shapes_unpack_f64` backends: serial
- `nk_shapes_unpack_f32` backends: serial
- `nk_shapes_unpack_f16` backends: serial

Unpacking an absolute pack returns its inputs within one storage rounding, and a normalized pack its cloud at the original scale.

- `packed` (in): A pack of `f64` points, plain or normalized.
- `points` (out): The cloud, its points × 3 interleaved [x0,y0,z0, x1,y1,z1, ...].
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_rmsds_packed_*_best

Superposed RMSD between every row and every column frame, matched point by point.

```c
nk_status_t nk_rmsds_packed_f64_best(
    void const *const *a_packed,
    nk_size_t rows,
    void const *const *b_packed,
    nk_size_t columns,
    nk_f64_t *results,
    nk_size_t results_stride,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_rmsds_packed_f64_best`: results `nk_f64_t`
- `nk_rmsds_packed_f32_best`: results `nk_f64_t`
- `nk_rmsds_packed_f16_best`: results `nk_f32_t`

- `nk_rmsds_packed_f64` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_rmsds_packed_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_rmsds_packed_f16` backends: serial, haswell, skylake, v128relaxed, neon, neonfhm, rvv, blackwell, metal, apple10, cuda, rocm

Each is √(min_R Σ‖aᵢ − R × bᵢ‖² / n) over proper rotations R, and zero between empty frames.

- `a_packed` (in): The row shape packs, plain or normalized, `rows` pointers.
- `rows` (in): The number of row frames.
- `b_packed` (in): The column packs, `columns` pointers.
- `columns` (in): The number of column frames.
- `results` (out): The ____[rows,columns] distances.
- `results_stride` (in): The bytes between consecutive rows of `results`.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_unexpected_dimensions_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_unexpected_dimensions_k) The packs differ in points, on a capability whose host reads them. On [`nk_cap_cuda_k`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cap_cuda_k), [`nk_cap_rocm_k`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cap_rocm_k) and [`nk_cap_blackwell_k`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cap_blackwell_k), whose host cannot, such a pair instead reports NaN in `results`.
- [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.
- [`nk_device_memory_mismatch_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_device_memory_mismatch_k) A GPU capability's device cannot reach the pack tables, outputs or `workspace`, which the runtime answers per call.

## nk_hausdorffs_packed_*_best

Hausdorff distances between every row and every column cloud, the best of 8 axis flips.

```c
nk_status_t nk_hausdorffs_packed_f64_best(
    void const *const *a_packed,
    nk_size_t rows,
    void const *const *b_packed,
    nk_size_t columns,
    nk_f64_t *results,
    nk_size_t results_stride,
    nk_f32_t *rotations,
    nk_f64_t const *limit,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_hausdorffs_packed_f64_best`: results, limit `nk_f64_t`
- `nk_hausdorffs_packed_f32_best`: results, limit `nk_f64_t`
- `nk_hausdorffs_packed_f16_best`: results, limit `nk_f32_t`

- `nk_hausdorffs_packed_f64` backends: serial, skylake
- `nk_hausdorffs_packed_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_hausdorffs_packed_f16` backends: serial, haswell, skylake, v128relaxed, neon, neonfhm, rvv, blackwell, metal, apple10, cuda, rocm

Each is max(maxᵢ minⱼ ‖aᵢ − S × bⱼ‖, maxⱼ minᵢ ‖aᵢ − S × bⱼ‖), zero between two empty clouds and infinite between an empty and a non-empty one.

- `a_packed` (in): The row packs, `rows` pointers.
- `rows` (in): The number of row clouds.
- `b_packed` (in): The column packs, `columns` pointers.
- `columns` (in): The number of column clouds.
- `results` (out): The ____[rows,columns] distances.
- `results_stride` (in): The bytes between consecutive rows of `results`.
- `rotations` (out): Null, or the ____[rows,columns,3,3] rotations taking each column cloud onto each row cloud.
- `limit` (in): Distances up to it are exact; past it, a pair may stop early and report a lower bound that also lies past it, with that bound's flip in `rotations`. Infinity scores all.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.
- [`nk_device_memory_mismatch_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_device_memory_mismatch_k) A GPU capability's device cannot reach the pack tables or outputs, which the runtime answers per call.

## nk_chamfers_packed_*_best

Chamfer distances between every row and every column cloud, the best of 8 axis flips.

```c
nk_status_t nk_chamfers_packed_f64_best(
    void const *const *a_packed,
    nk_size_t rows,
    void const *const *b_packed,
    nk_size_t columns,
    nk_f64_t *results,
    nk_size_t results_stride,
    nk_f32_t *rotations,
    nk_f64_t const *limit,
    void *workspace,
    nk_size_t workspace_bytes,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_chamfers_packed_f64_best`: results, limit `nk_f64_t`
- `nk_chamfers_packed_f32_best`: results, limit `nk_f64_t`
- `nk_chamfers_packed_f16_best`: results, limit `nk_f32_t`

- `nk_chamfers_packed_f64` backends: serial, skylake
- `nk_chamfers_packed_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_chamfers_packed_f16` backends: serial, haswell, skylake, v128relaxed, neon, neonfhm, rvv, blackwell, metal, apple10, cuda, rocm

Each is ½ × (meanᵢ minⱼ ‖aᵢ − S × bⱼ‖ + meanⱼ minᵢ ‖aᵢ − S × bⱼ‖), with the same empty-cloud convention as [`nk_hausdorffs_packed_f64_best`](#nk_hausdorffs_packed_f64_best).

- `a_packed` (in): The row packs, `rows` pointers.
- `rows` (in): The number of row clouds.
- `b_packed` (in): The column packs, `columns` pointers.
- `columns` (in): The number of column clouds.
- `results` (out): The ____[rows,columns] distances.
- `results_stride` (in): The bytes between consecutive rows of `results`.
- `rotations` (out): Null, or the ____[rows,columns,3,3] rotations taking each column cloud onto each row cloud.
- `limit` (in): Distances up to it are exact; past it, a pair may stop early and report a lower bound that also lies past it, with that bound's flip in `rotations`. Infinity scores all.
- `workspace` (inout): Scratch of `workspace_bytes` on an `nk_default_alignment_k` boundary, as [`nk_chamfers_workspace_size_f64_best`](#nk_chamfers_workspace_size_f64_best) reports for the largest clouds.
- `workspace_bytes` (in): The bytes of `workspace`.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.
- [`nk_unexpected_dimensions_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_unexpected_dimensions_k) A capability that needs `workspace` found it too short.
- [`nk_misaligned_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_misaligned_k) A capability that needs `workspace` found it off its boundary.
- [`nk_device_memory_mismatch_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_device_memory_mismatch_k) A GPU capability's device cannot reach the pack tables, outputs or `workspace`, which the runtime answers per call.

## nk_chamfers_workspace_size_*_best

Reports the workspace bytes [`nk_chamfers_packed_f64_best`](#nk_chamfers_packed_f64_best) needs between clouds of up to `row_points` and `column_points` points.

```c
nk_status_t nk_chamfers_workspace_size_f64_best(
    nk_size_t row_points,
    nk_size_t column_points,
    nk_capability_t capabilities,
    nk_size_t *bytes);
```

- `nk_chamfers_workspace_size_f64_best`
- `nk_chamfers_workspace_size_f32_best`
- `nk_chamfers_workspace_size_f16_best`

- `nk_chamfers_workspace_size_f64` backends: serial
- `nk_chamfers_workspace_size_f32` backends: serial, cuda, rocm
- `nk_chamfers_workspace_size_f16` backends: serial, blackwell, metal, apple10, cuda, rocm

- `row_points` (in): The most points in any row cloud.
- `column_points` (in): The most points in any column cloud.
- `capabilities` (in): One device's capabilities.
- `bytes` (out): The workspace bytes.

## nk_wassersteinprocrustes_workspace_size_*_best

Returns the workspace bytes the transport kinds need between clouds of up to `row_points` and `column_points` points.

```c
nk_status_t nk_wassersteinprocrustes_workspace_size_f64_best(
    nk_size_t row_points,
    nk_size_t column_points,
    nk_capability_t capabilities,
    nk_size_t *bytes);
```

- `nk_wassersteinprocrustes_workspace_size_f64_best`
- `nk_wassersteinprocrustes_workspace_size_f32_best`
- `nk_wassersteinprocrustes_workspace_size_f16_best`

- `nk_wassersteinprocrustes_workspace_size_f64` backends: serial
- `nk_wassersteinprocrustes_workspace_size_f32` backends: serial, cuda, rocm
- `nk_wassersteinprocrustes_workspace_size_f16` backends: serial, metal, cuda, rocm

- `row_points` (in): The largest row cloud.
- `column_points` (in): The largest column cloud.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `bytes` (out): The size of the workspace.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_wassersteinprocrustes_packed_*_best

Wasserstein-Procrustes distances between every row cloud and every column cloud.

```c
nk_status_t nk_wassersteinprocrustes_packed_f64_best(
    void const *const *a_packed,
    nk_size_t rows,
    void const *const *b_packed,
    nk_size_t columns,
    nk_f64_t *results,
    nk_size_t results_stride,
    nk_f32_t *rotations,
    void *workspace,
    nk_size_t workspace_bytes,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_wassersteinprocrustes_packed_f64_best`: results `nk_f64_t`
- `nk_wassersteinprocrustes_packed_f32_best`: results `nk_f64_t`
- `nk_wassersteinprocrustes_packed_f16_best`: results `nk_f32_t`

- `nk_wassersteinprocrustes_packed_f64` backends: serial, skylake
- `nk_wassersteinprocrustes_packed_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_wassersteinprocrustes_packed_f16` backends: serial, haswell, skylake, v128relaxed, neon, rvv, metal, cuda, rocm

Each is the root mean squared distance of an exact optimal transport plan between uniform weights under a rotation or reflection, the best of the local optima that alternating the two reaches from the 8 axis flips, zero between two empty clouds and infinite between an empty and a non-empty one. Other parameters match the Chamfer kernel's.

- `a_packed` (in): The row packs, `rows` pointers.
- `rows` (in): The number of row clouds.
- `b_packed` (in): The column packs, `columns` pointers.
- `columns` (in): The number of column clouds.
- `results` (out): The ____[rows,columns] distances.
- `results_stride` (in): The bytes between consecutive rows of `results`.
- `rotations` (out): Null, or the ____[rows,columns,3,3] rotations taking each column cloud onto each row cloud.
- `workspace` (in): The scratch memory, aligned to `nk_default_alignment_k`.
- `workspace_bytes` (in): At least the bytes the matching workspace-size query reports for the largest clouds on each side.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `stream` (in): Null on the CPU, or the GPU stream of that device to queue on.

Statuses: - [`nk_misaligned_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_misaligned_k) A misaligned `workspace`.
- [`nk_unexpected_dimensions_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_unexpected_dimensions_k) `workspace_bytes` falls short.
- [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.
- [`nk_device_memory_mismatch_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_device_memory_mismatch_k) A GPU capability's device cannot reach the pack tables, outputs or `workspace`, which the runtime answers per call.

## nk_gromovwassersteins_workspace_size_*_best

Returns the workspace bytes the transport kinds need between clouds of up to `row_points` and `column_points` points.

```c
nk_status_t nk_gromovwassersteins_workspace_size_f64_best(
    nk_size_t row_points,
    nk_size_t column_points,
    nk_capability_t capabilities,
    nk_size_t *bytes);
```

- `nk_gromovwassersteins_workspace_size_f64_best`
- `nk_gromovwassersteins_workspace_size_f32_best`
- `nk_gromovwassersteins_workspace_size_f16_best`

- `nk_gromovwassersteins_workspace_size_f64` backends: serial
- `nk_gromovwassersteins_workspace_size_f32` backends: serial, cuda, rocm
- `nk_gromovwassersteins_workspace_size_f16` backends: serial, metal, cuda, rocm

- `row_points` (in): The largest row cloud.
- `column_points` (in): The largest column cloud.
- `capabilities` (in): One device's capabilities, like [`nk_cpu_capabilities_enabled`](https://www.unum.cloud/docs/numkong/c/runtime-dispatch.md#nk_cpu_capabilities_enabled) reports.
- `bytes` (out): The size of the workspace.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k) No capability in `capabilities` has it.

## nk_gromovwassersteins_packed_*_best

Gromov-Wasserstein distances between every row cloud and every column cloud.

```c
nk_status_t nk_gromovwassersteins_packed_f64_best(
    void const *const *a_packed,
    nk_size_t rows,
    void const *const *b_packed,
    nk_size_t columns,
    nk_f64_t *results,
    nk_size_t results_stride,
    nk_f32_t *rotations,
    void *workspace,
    nk_size_t workspace_bytes,
    nk_capability_t capabilities,
    nk_stream_t stream);
```

- `nk_gromovwassersteins_packed_f64_best`: results `nk_f64_t`
- `nk_gromovwassersteins_packed_f32_best`: results `nk_f64_t`
- `nk_gromovwassersteins_packed_f16_best`: results `nk_f32_t`

- `nk_gromovwassersteins_packed_f64` backends: serial, skylake
- `nk_gromovwassersteins_packed_f32` backends: serial, haswell, skylake, v128relaxed, neon, rvv, cuda, rocm
- `nk_gromovwassersteins_packed_f16` backends: serial, haswell, skylake, v128relaxed, neon, rvv, metal, cuda, rocm

Each compares the two clouds' plain distance matrices under the best transport plan reached from the Wasserstein-Procrustes plans and the product plan. The rotation is the orthogonal Procrustes fit of that plan. Parameters and returns match the Wasserstein-Procrustes kernel's, with the workspace sized by [`nk_gromovwassersteins_workspace_size_f64_best`](#nk_gromovwassersteins_workspace_size_f64_best).

## nk_shapes_output_dtype

Returns the distance dtype of the many-to-many shape kernels, as the C++ `shapes_distance_t` alias in types.hpp names it.

```c
nk_dtype_t nk_shapes_output_dtype(nk_dtype_t dtype);
```

## nk_rmsds_output_dtype

Returns the distance dtype of the superposed RMSD kernels.

```c
nk_dtype_t nk_rmsds_output_dtype(nk_dtype_t dtype);
```

## nk_shapes_find_kernel

Finds the shape kernel of `kind` for `dtype`, from the best of `capabilities`.

```c
nk_status_t nk_shapes_find_kernel(
    nk_kernel_kind_t kind,
    nk_dtype_t dtype,
    nk_capability_t capabilities,
    nk_kernel_punned_t *kernel,
    nk_capability_t *capability);
```

- `kind` (in): The verb whose kernel to find.
- `dtype` (in): The input type the kernel reads.
- `capabilities` (in): The mask to pick the best capability from.
- `kernel` (out): The kernel, or null when no capability in `capabilities` has it.
- `capability` (out): The capability the kernel belongs to, or zero.

Statuses: - [`nk_missing_kernel_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_kernel_k)
- [`nk_missing_library_k`](https://www.unum.cloud/docs/numkong/c/quickstart.md#nk_missing_library_k) Header-only.

