Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

TorchPairwise / documentation

Comparisons and
Streamed Operators

Distances, divergences, kernels and scores for tensor samples. One comparison API, four pairing modes, shared native execution in Python and C++.

87 comparison entries13 kernels9 input familiesCPU / CUDA
Four comparison modes: all-pairs matrix, paired values, condensed upper triangle and one single-sample value. Streaming computes tiles and combines them into the requested result.

A Comparison in Either Language

The top-bar Python / C++ switch changes paired examples throughout this guide. Use the same metric name and mode in both languages.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[0., 0.], [3., 4.]])  # [N=2,D=2]
Y = torch.tensor([[0., 4.], [3., 0.]])  # [M=2,D=2]

scores = tp.compare(X, Y, metric="sqeuclidean")  # [2,2]
C++ / LibTorch
#include <torchpairwise.h>
namespace tp = torchpairwise::ops;

auto X = at::tensor({0.f, 0.f, 3.f, 4.f}).reshape({2, 2});
auto Y = at::tensor({0.f, 4.f, 3.f, 0.f}).reshape({2, 2});
auto scores = tp::compare(X, Y, "sqeuclidean");  // [2,2]

Result: [[16, 9], [9, 16]].

Start with the quick start, or browse the comparison catalog. Both numeric and nonnumeric samples use ordinary dense tensors; structured distributions group their component tensors.

Explore the example gallery for kernel ridge regression, truncated kernel PCA, kernel LDA, binary SVM, landmark regression, PageRank and kernel fitting with an intercept, graph diffusion and matrix-function actions. Each pairs a streamed algorithm with a small dense correctness reference.

Streamed sources also support pair masks, entropic transport over any lazy cost, Gaussian process prediction, user-defined metrics, encoded features with one or two encoders, and optional execution backends.

This guide documents TorchPairwise 1.0. See installation for matching your PyTorch build and migration when upgrading.

Python and native callers share built-in forward and first-backward algorithms. Metric-specific domains, autocast, compilation and boundary derivatives are part of each comparison's contract. A symmetric entry is not necessarily a triangle-inequality metric, and a score need not be a positive-semidefinite kernel.

Getting started

Install and Build

Use a compatible PyTorch or LibTorch installation. Native extensions must match the PyTorch major/minor, C++ ABI and platform toolchain. CUDA execution also requires a compatible CUDA build and NVIDIA driver.

Python Source Installation

These examples target TorchPairwise 1.0. Install PyTorch for your platform first, then build this checkout against that installation:

git clone https://github.com/inspiros/torchpairwise.git
cd torchpairwise
python -m pip install -e . --no-build-isolation

The build detects CUDA when a compatible toolkit is available. See the source build guide for CPU-only builds, toolchain options and matching architectures.

Standalone C++

Build and install the CMake package against your LibTorch prefix:

cmake -S . -B build/native \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_PREFIX_PATH=/path/to/libtorch \
  -DCMAKE_INSTALL_PREFIX=/path/to/torchpairwise-install
cmake --build build/native --parallel 2
cmake --install build/native

For specialized CUDA kernels, enable TORCHPAIRWISE_WITH_CUDA=ON and choose compatible CUDA architectures. A build linked to CUDA LibTorch inherits its CUDA dependencies even if custom kernels are omitted. See the CMake guide.

Consume the exported target:

find_package(torchpairwise CONFIG REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE torchpairwise::torchpairwise)

Include <torchpairwise.h>. The installed target carries native headers, C++20 and LibTorch linkage. Arrange runtime library search paths for both LibTorch and torchpairwise. No Python runtime is needed for built-in C++ execution.

Getting started

Quick Start

Choose a formula, then a pairing mode. These small squared-Euclidean examples have the same values in Python and C++. N and M are the collection sizes; D is the number of features in one vector.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[0., 0.], [3., 4.]])  # [N=2,D=2]
Y = torch.tensor([[0., 4.], [3., 0.]])  # [M=2,D=2]
metric = "sqeuclidean"

matrix = tp.compare(X, Y, metric=metric)                       # [2,2]
paired = tp.compare(X, Y, metric=metric, mode="paired")        # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")     # [1]
single = tp.compare(X[0], Y[0], metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>
namespace tp = torchpairwise::ops;

auto X = at::tensor({0.f, 0.f, 3.f, 4.f}).reshape({2, 2});
auto Y = at::tensor({0.f, 4.f, 3.f, 0.f}).reshape({2, 2});
const std::string metric = "sqeuclidean";

auto matrix = tp::compare(X, Y, metric);                             // [2,2]
auto paired = tp::compare(X, Y, metric, "paired");                   // [2]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed");  // [1]
auto single = tp::compare(X[0], Y[0], metric, "single");             // scalar
ModeValuesShape
all-pairs[[16,9],[9,16]][2,2]
paired[16,16][2]
condensed[25][1]
single16scalar

Condensed compares X with itself, taking only i<j. Single inputs omit the collection axis. single_comparison is an optional convenience for the same single mode. See modes and shapes for batches and structured samples.

A Streamed Result

If the algorithm needs a reduction or selected neighbors, bind the formula instead of returning every comparison. Here the logical source is [2,2]; the consumer returns [2].

Python
A = tp.lazy(X, Y, metric="sqeuclidean", chunk_size=1)
row_sums = A.sum(dim=-1)              # [2], values [25,25]
neighbors = A.topk(1, largest=False)  # values/indices [2,1]
C++ / LibTorch
tp::PairwiseOperationOptions options;
options.chunk_size = 1;
auto A = tp::pairwise_expression(X, Y, "sqeuclidean", false, options);
auto row_sums = A.reduce("sum", -1);            // [2], values [25,25]
auto [values, indices] = A.topk(1, -1, false);  // [2,1] each

The nearest values are [[9],[9]], with reference indices [[1],[0]]. Bindings retain input references and options, not computed matrices. Each consumer evaluates the necessary tiles again. Continue with sources and consumers.

Getting started

Using Torchpairwise in Models

Many models compute a pairwise score matrix and immediately reduce it: attention normalizes and applies it, a contrastive loss takes a row log-sum-exp, a kernel method solves with it, and retrieval keeps a few best entries. In PyTorch, each of these allocates the full [N,M] matrix and keeps it for backward. In torchpairwise, you bind the scores with tp.lazy, then call the consumer. The consumer evaluates the pairs in bounded tiles, or on fused CUDA kernels, and never stores the matrix. First derivatives replay the tiles instead of saving them.

import torch
import torchpairwise as tp

scores = tp.lazy(Q, K, metric="linear") * Q.shape[-1] ** -0.5  # [N,M], not stored
out = scores.softmax_matmul(V)                                 # [N,C]
out.sum().backward()                                           # grads for Q, K, V

Memory then scales with the inputs and outputs, (N+M)*(D+C), plus a tile workspace. The time cost is unchanged: every pair is still evaluated, and backward evaluates them again. Streaming pays off when N*M is the memory bottleneck, not when the matrix is small.

Task Map

Each row is a native operation with first-order autograd on CPU and CUDA. "Fused" marks consumers that run on fused CUDA kernels for float32 or float64 inputs with 1 to 64 features and a supported source; see execution backends.

TaskCallNotes
Attention(tp.lazy(Q, K, "linear") * s).softmax_matmul(V, mask=...)Fused without a pair mask; masks: tp.masks.causal(), band, same_group
Kernel attention, Nadaraya-Watsontp.lazy(X, Y, "rbf", gamma=g) @ torch.cat([V, ones], -1)Fused; the last column is the row mass
InfoNCE, NT-XentS.logsumexp(-1, exclude_self=True) on S = tp.lazy(z, z, "linear") / tFused; recipe in tests/test_ml_recipes.py
k-NN, retrievaltp.lazy(X, R, "sqeuclidean").topk(k, largest=False)Fused, exact tie order; argmin, min
Chamfer-style lossesD.min(-1) and D.min(-2) on D = tp.lazy(X, Y, "sqeuclidean")min returns values and indices
MMD loss, two-sample testtp.mmd_statistics(X[None], Y[None], kernel="rbf", gamma=g)Unbiased by default
HSIC, CKAKx.hsic(Ky), Kx.frobenius_inner_product(Ky, center=True, normalize=True)Two lazy kernels on the same samples
Kernel ridge, GP meantp.linalg.solve(K + a * tp.eye_like(K), Y, method="cg")Preconditioners: tp.linalg.pivoted_cholesky, Nyström
GP variancetp.linalg.love_cache, tp.linalg.predictive_varianceSee GP prediction
Kernel PCA, spectral embeddingtp.linalg.eigh(K.centered(), k=r)Low-rank: tp.linalg.svd
Kernel SVMtp.optimize.box_qp(Q, q, lower=0., upper=C, equality=...)See the example gallery
Optimal transport losstp.optimize.sinkhorn_divergence(C_xy, C_xx, C_yy, a, b, epsilon=e)Any lazy cost; see entropic transport
Landmarks, coresetstp.neighbors.farthest_point_sampling(D, k), tp.neighbors.kmeans_plusplus(D, k)Greedy selection; returns indices
Learned metricstp.make_pairwise(fn), encoder headsSee custom comparisons

X[None] in the MMD row is not a typo: mmd_statistics compares batches of point clouds, so one feature set is one cloud.

Score Expressions

Build scores with ordinary arithmetic on the lazy object, then consume once. Offsets and weights per row or column (add_rows, add_columns, weight_rows, weight_columns), exp, and boolean row/column masks stay fused on CUDA. Their row and column tensors can require gradients, so learned biases and per-sample weights train through the same streamed backward.

D = tp.lazy(X, Y, metric="sqeuclidean")
logits = (-0.5 / tau * D).add_rows(row_bias).add_columns(column_bias)
loss = -logits.logsumexp(-1).mean()  # fused on float32 CUDA

Pair masks from tp.masks (causal, band, same_group) and other expression nodes such as sqrt, pow or minimum work on every consumer but use tiles.

Stein Variational Gradient Descent

SVGD moves particles x toward a target density p with the direction

\[ \phi(x_i)=\frac{1}{n}\sum_j k(x_j,x_i)\nabla\log p(x_j)+\nabla_{x_j}k(x_j,x_i) \]

For an RBF kernel exp(-gamma*|x-y|^2), the repulsive term is -2*gamma*sum_j k_ij*(x_j-x_i), which the kernel's displacement method streams. One update therefore takes one kernel product and one displacement, with no [n,n] matrix:

def svgd_direction(x, score, *, gamma):
    K = tp.lazy(x, x, metric="rbf", gamma=gamma)
    return (K @ score - 2 * gamma * K.displacement()) / x.shape[-2]

for _ in range(steps):
    x = x + step_size * svgd_direction(x, score_fn(x), gamma=gamma)

tests/test_ml_recipes.py checks this direction against a dense autograd oracle. A kernel Stein discrepancy also needs score inner products and the kernel's second derivatives per pair. No single consumer computes them, so the library does not provide one.

What to Check

  • Precision. Fused CUDA kernels sum in a different order from the tiles (and from the matrix product behind dot-product scores), so results agree within floating tolerance, not bitwise. See precision and deployment.
  • Gradients. Supported vector comparisons and streamed sums, means, matrix products, logsumexp and softmax products support second derivatives, including gradient penalties and Hessian-vector products. Higher-order replay retains tile intermediates and may use O(N*M) memory. Selections (topk, argmin) route gradients through the selected pairs only, not the indices. Other operations may raise for higher derivatives.
  • Solvers. Results carry converged and status; check them before using solution. Differentiable solves need differentiable=True.
  • Deployment. torch.compile, torch.export and LibTorch run the same native operations, including the fused kernels. The optional KeOps backend is Python-only and eager; see execution backends.
Getting started

Modes and Shapes

A metric describes how to compare two samples. A mode describes which samples are compared. Batch axes are independent of that choice.

For a scalar comparison \(f(x,y)\):

\[ A_{ij}=f(X_i,Y_j),\qquad p_i=f(X_i,Y_i),\qquad c_{ij}=f(X_i,X_j)\ (i<j),\qquad s=f(x,y)\]
ModeVector inputsOutputMeaning
all-pairs[...,N,D], [...,M,D][...,N,M]Every X/Y combination; omitted Y means self comparison
paired[...,N,D], [...,N,D][...,N]Equal sample counts; both collections required
condensed[...,N,D][...,N*(N-1)//2]Unordered self pairs, no diagonal; supported symmetric comparisons
single[...,D], [...,D][...]One sample per broadcast batch; both samples required

Condensed ordering is (0,1), (0,2), …, (1,2), …. Do not interpret pdist as corresponding rows. Use paired mode for that operation.

What Is One Sample?

FamilyOne sampleCollection X
Vectors, histograms[D][...,N,D]
Point sets[P,D][...,N,P,D]
Images[C,H,W][...,N,C,H,W]
Audio[C,T][...,N,C,T]
Sequences[T,D][...,N,T,D]
Token sequences[T][...,N,T]
Covariances[D,D][...,N,D,D]
Gaussians(mean[D], covariance[D,D])(means[...,N,D], covariances[...,N,D,D])
Gaussian mixtures(means[K,D], covariances[K,D,D], weights[K])Add the N axis before each component's sample dimensions

Point sets are dense tensors, not sparse storage. A cloud may contain coordinates or feature vectors. Whole-image comparisons instead preserve aligned channels and spatial axes; they do not turn pixels into unordered points.

Leading Batches

Leading dimensions follow PyTorch broadcasting. All-pairs vectors shaped [2,1,N,D] and [3,M,D] produce [2,3,N,M]. In single mode, [B,D] versus [D] produces [B]; the same [B,D] in all-pairs mode is a collection of B vectors. No mode is inferred from shape.

Sample and feature axes must meet the metric's contract. Paired sample counts must match exactly; an N=1 collection does not broadcast to N corresponding samples. Point counts or sequence lengths can differ where documented.

Lengths and Structured Inputs

Point-set, sequence, token and mixture comparisons can accept valid prefix lengths. Collection lengths end in [N]; single-mode lengths omit that axis: use a scalar for one unbatched sample or [B] for B batched samples. Token lengths may be zero; point distributions must be nonempty. Masked padding is ignored according to the family contract.

Single point sets use canonical [P,D] samples. Flatten spatial point grids first; omit the collection-oriented batched and feature_dim controls. Use a tuple/ComparisonCollection for Gaussian or mixture components, not an extra synthetic feature dimension. See each metric page.

Getting started

Comparison Selection

compare covers all scalar roles: distances, divergences, costs, kernels, scores and statistics. Full canonical names are clearest across families. Modes and parameters are checked against one native catalog in both languages.

SelectorCatalog scope
compare87 entries, all paired/single; 76 condensed
cdist / pairwise_distances68 distances, divergences and costs
paired_distancesSame distance role, paired samples
pdist / condensed_distances59 supported symmetric distance entries
pairwise_kernels, paired_kernels, condensed_kernels13 kernels, including Gaussian-mixture overlap
lazyThe 87 scalar formulas, streamed

Counts include mathematically equivalent named entries, not just distinct formulas. list_comparisons(family=..., role=...) exposes names, aliases, sample layouts, modes and accepted parameters in Python; use the searchable catalog in either language.

Named Functions and Aliases

Unprefixed named functions retain all-pairs behavior. Some also expose paired_ / condensed_ functions. Use the generic mode selector for consistent access rather than guessing a named function. single_comparison is a shorthand for compare(...,mode="single"), not another numerical implementation.

Shared short names are role-dependent. cosine means distance in compare or a distance selector, similarity in a kernel selector or lazy. Use cosine_distances and cosine_similarity when switching roles. MMD kernel parameters have their own conventions.

Defaults and Options

compare, single_comparison, pairwise_distances, paired_distances and lazy default to Euclidean. cdist, pdist and condensed_distances retain Minkowski with p=2. Default values agree, but accepted parameters differ: specify a Minkowski metric when passing p.

Python uses named keyword parameters; native C++ uses ComparisonOptions. Omitted parameters differ from explicitly supplied ones. Parameters unused by a metric are errors. Scalar controls, metric names and modes are structural choices; they are not learnable tensor parameters.

All generic selectors return one Tensor. Named directed Hausdorff additionally returns witness indices; use the named API when those are needed.

Native Calls

Python cdist/pdist call the same native schemas as C++ wrappers. Gaussian/mixture inputs use collection overloads. Family numerical operators provide shared forward and first-backward execution. Native opaque operators and metadata support compilation/export; see deployment.

Streamed operators

Sources and Consumers

A source describes a logical array. A consumer evaluates tiles and combines them into the requested Tensor. For scalar comparisons, the logical shape is \([\ldots,N,M]\). No dense generated matrix is retained merely by binding it.

Use tp.lazy to bind scalar comparisons in Python and torchpairwise::streaming in C++. Custom metrics and feature comparisons can also be passed to tp.lazy.

Python
# X: [N,D], Y: [M,D], V: [M,C]
A = tp.lazy(X, Y, metric="sqeuclidean")
row_sum = A.sum(dim=-1)                     # [N]
values, indices = A.topk(8, largest=False)  # [N,8] each
product = A.matmul(V)                       # [N,C]
C++ / LibTorch
// X:[N,D], Y:[M,D], V:[M,C]
namespace s = torchpairwise::streaming;
s::PairwiseOptions options;
options.chunk_size = 128;
auto A = s::pairwise(X, Y, "sqeuclidean", options);
auto row_sum = A.reduce("sum", -1);             // [N]
auto [values, indices] = A.topk(8, -1, false);  // [N,8] each
auto product = A.matmul(V);                     // [N,C]

The examples assume at least eight references. The language switch changes the frontend spelling; mathematical results and native algorithms are shared.

Source Families

SourceLogical arrayPythonC++
Scalar vector/cloud[...,N,M]tp.lazystreaming::pairwise
Kernels[...,N,M]tp.lazystreaming::kernel with vector/structured options
Gaussian/covariance/mixture[...,N,M]tp.lazystreaming::pairwise with structured options
Images/audio[...,N,M]tp.lazystreaming::pairwise with media options
Sequences/tokens/MMD statistic[...,N,M]tp.lazystreaming::pairwise with ordered options
Existing caller matrix[...,N,M]tp.as_lazystreaming::matrix
User-defined scalar comparison[...,N,M]tp.lazy(x, y, comparison)custom_expression with registered user operators
Encoded features with a head[...,N,M]tp.lazy(x, y, feature_comparison)Run the encoder, then bind the feature tensors
Feature residuals[...,N,M,D]tp.residual_operatorstreaming::residual
Image maps[...,N,M,C,Hout,Wout]tp.image_map_operatorstreaming::image_map

C++ family factories use typed family options; the common ScalarExpression consumer interface applies after binding. See native options. Wrapping an existing matrix does not remove its input storage.

Shared Scalar Consumers

PurposeMethods
Products and formsmatmul, softmax_matmul, bilinear, quadratic, frobenius_inner_product, hsic
Selected entriesdiagonal, block, topk
Reductionssum, mean, min, max, prod, all, any
Statisticsvar, std, logsumexp, count_nonzero, weighted_mean, weighted_var, weighted_std, median, quantile

These are 25 scalar consumers. Python method names mirror Tensor operations; C++ uses reduce("sum",...) and named statistics on ScalarExpression. Top-k returns values and indices. Python min(dim) and max(dim) return values and indices; amin and amax return values. Unlike PyTorch, median(dim) returns values only. See reductions for dimensions and defaults.

Python also provides argmin, argmax, amin, amax and softmax. Reductions, selections and softmax_matmul accept exclude_self and, where meaningful, a pair mask. Entrywise lazy objects support these consumers. Linear-only objects, including products of lazy objects, softmax, identity, diagonal and block maps, group projections and preconditioners, support @, .mT, sum, mean, scalar multiplication, addition and subtraction with lazy objects, and the solvers. Other consumers raise with a .to_dense() hint.

Execution Backends

Tiled native execution is the default and is shared by Python, C++, compilation and export. Two accelerations are layered on top of it:

  • Fused CUDA reducers apply automatically to float32 and float64 CUDA sources with 1 to 64 features. They cover sqeuclidean, manhattan, chebyshev, minkowski with p other than 2, rbf, laplacian, matern, rational_quadratic, linear, polynomial and sigmoid with scalar parameters (selections on the last three use tiles), under matmul, sum, mean, logsumexp, softmax_matmul and top-k or min/max selection, including number, row and column offsets, weights, exp and row/column boolean masks. They evaluate each pair and reduce it in one kernel, so chunk_size is ignored; tp.masks pair masks use tiles.
  • KeOps is an optional Python backend for eager, unbatched float32/float64 vector distances, kernels and scalar expressions. Install it with pip install "torchpairwise[keops]". Select it per context or per source: "native", "keops" or "auto". Unsupported sources, options and consumers fall back to native execution.
with tp.backend("keops"):
    K = tp.lazy(X, Y, metric="rbf", gamma=0.5)
    result = K.matmul(V)  # one KeOps reduction

K = tp.lazy(X, Y, metric="rbf", gamma=0.5, backend="native")  # leaf override

backend="keops" raises when KeOps is not installed, while "auto" falls back silently and only selects large float32 CUDA workloads. torch.compile, torch.export, standalone LibTorch and tp.linalg solvers always run native code. Results agree within floating tolerance, not bitwise, and selection tie order is unspecified under "keops". See the fused reducer rules and KeOps backend contract.

Memory and Chunking

chunk_size controls outer sample tiles. feature_chunk_size slices residual features; metric/component controls bound inner cloud or mixture work. Widths are arbitrary positive integers up to int64max, not powers-of-two presets. They are not a total-memory budget.

Inputs, prepared features, output buffers and gradients remain in memory. Exact median/quantile may sort whole rows in budgeted row blocks. Training uses bounded replay for supported sources: save source inputs and compact state, recompute source tiles during backward, accumulate their adjoints. Recomputing costs time; choose by workload rather than assuming streaming is always faster.

Bindings store references and options, not feature/matrix caches. Each consumer recomputes; input mutations are visible. Ordinary Tensor operations after a consumer do not stream back into its source. This is a finite comparison and consumer API, not an arbitrary lazy PyTorch execution system.

Streamed operators

Algebra and Kernel Methods

The example gallery connects these operators to kernel ridge regression, PCA, LDA, SVM, landmark regression and PageRank, with shapes and runnable dense checks.

Scalar source algebra transforms entries before a consumer. It supports arithmetic with sources, tensors and numbers, powers, square, exp, log, sqrt, trigonometric and sigmoid maps, minimum, maximum and clamping, comparisons, where, masking and row/column weighting. Shapes must obey the operator contracts; this is not lazy matrix composition. See the operator algebra notes for the complete list, and pair masks for eligibility-based consumers.

Python
# X:[N,D], Y:[M,D], V:[M,C]
K1 = tp.lazy(X, Y, metric="rbf", gamma=0.2)
K2 = tp.lazy(X, Y, metric="laplacian", gamma=0.3)
K = 0.7 * K1 + 0.3 * K2
features = K.matmul(V)               # [N,C]
energy = K.bilinear(left, right)     # scalar for left[N], right[M]
soft_features = K.softmax_matmul(V)  # [N,C]
C++ / LibTorch
// X:[N,D], Y:[M,D], V:[M,C]
tp::PairwiseOperationOptions first, second;
first.comparison.gamma = 0.2;
second.comparison.gamma = 0.3;
auto K1 = tp::pairwise_expression(X, Y, "rbf", true, first);
auto K2 = tp::pairwise_expression(X, Y, "laplacian", true, second);
auto K = K1.scale(0.7) + K2.scale(0.3);
auto features = K.matmul(V);               // [N,C]
auto energy = K.bilinear(left, right);     // left[N], right[M] -> scalar
auto soft_features = K.softmax_matmul(V);  // [N,C]

Products and Scalar Forms

\[ (AV)_{ic}=\sum_jA_{ij}V_{jc},\qquad B=L^\mathsf{T}AR,\qquad Q=V^\mathsf{T}AV\]

matmul gives the product; bilinear computes B without retaining A or AR; quadratic gives Q for a square source. Symmetry and PSD are not inferred. softmax_matmul normalizes each source row over references before multiplying.

Linear Maps

Lazy objects support matrix composition alongside entrywise source algebra. Both sample axes can be centered, and square maps can receive a diagonal shift.

Python
import torchpairwise as tp
# X:[N,D], Y:[M,D], V:[N,R]
K = tp.lazy(X, Y, metric="rbf", gamma=0.2)
A = K.centered()  # H_N K H_M, [N,M]
B = A @ A.mT
B = B + 0.01 * tp.eye_like(B)  # [N,N]
result = B.matmul(V)           # [N,R]
C++ / LibTorch
namespace s = torchpairwise::streaming;
s::PairwiseOptions options;
options.comparison.gamma = 0.2;
auto K = s::kernel(X, Y, "rbf", options);
auto A = s::aslinearoperator(K).centered();
auto B = A.compose(A.transpose()).add_diagonal(0.01);
auto result = B.matmul(V);  // [N,R]

A @ B composes two lazy objects; A @ V applies one to a Tensor. Centering uses input/output sample means, while a diagonal shift adds a scaled RHS:

\[ H_L=I-\mathbf1\mathbf1^\mathsf{T}/L,\qquad A_cV=H_N\bigl(A(H_MV)\bigr),\qquad (A+\alpha I)V=AV+\alpha V \]

Keep the entrywise source for entrywise square, products, statistics and selection. Linear maps support composition, transpose, addition/subtraction, scalar scaling, centering (including fitted A.centered(reference_mean) for held-out kernels) and diagonal shifts, with matching float32/float64 inputs and broadcast batches. Factor tensors may receive gradients. The map does not form its matrix product, but intermediate vector blocks still use storage and may be retained for backward. Reconstruct maps after resizing bound tensors. See full linear-map contracts and the dense-checked example.

Block Maps

tp.block([[A,B],[C,D]]) combines an explicit rectangular grid of maps. Each block row shares a height and each block column a width; batches broadcast, with matching float32/float64 dtype/device. Entries can be maps, scalar sources or dense tensors in Python. In C++, adapt entries with s::aslinearoperator and use s::block({{A,B},{C,D}}). torch.hstack((A, B)) and torch.vstack((A, B)) expose the two building operations. Empty sample partitions are allowed; the grid itself must be nonempty. Scalar/None placeholders are unsupported.

\[ \begin{bmatrix}A&B\\C&D\end{bmatrix} \begin{bmatrix}u\\v\end{bmatrix} =\begin{bmatrix}Au+Bv\\Cu+Dv\end{bmatrix} \]

Actions split RHS blocks and combine native products without constructing a full block matrix or large zero padding. Transpose, first gradients, compile/export and native solvers use the finite block graph. Explicit dense blocks and intermediate result blocks still use memory; shared descriptions do not imply cached products. See block contracts and kernel fitting with an intercept.

Choosing a Solver

Use tp.linalg.solve for a square system and tp.linalg.lstsq for general least squares. Both select existing native algorithms and retain convergence diagnostics. They do not construct an inverse or pseudoinverse.

EntryMethodAssumptionExtra Options
solve"cg"Real symmetric positive definitepreconditioner
solve"minres"Real symmetric, possibly indefiniteNone
solve"gmres"General real squarerestart (default 20)
lstsq"lsqr" (default)General real rectangular/squaredamp (default 0)

solve requires an explicit method. There is no automatic method detection or fallback. Unsupported options raise; preconditioning belongs to CG and restart to GMRES. Common controls are rtol, atol, max_iter and initial. Inputs use matching float32/64 dtype/device with broadcast batches. Python accepts a map, scalar streaming source or dense Tensor; C++ adapts sources with aslinearoperator. For compile/export, the method string is static.

Python
# X:[N,D], targets:[N,R], landmarks:[L,D]
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.3)
    A = K + 0.2 * tp.eye_like(K)
    fit = tp.linalg.solve(A, targets, method="cg", rtol=1e-6)
    if not fit.converged.all():
        raise RuntimeError(f"CG status: {fit.status.tolist()}")
    coefficients = fit.solution  # [N,R]
    rectangular = tp.lazy(X, landmarks, metric="rbf", gamma=0.3)
    least = tp.linalg.lstsq(rectangular, targets, damp=0.2, rtol=1e-6)
    if not least.converged.all():
        raise RuntimeError(f"LSQR status: {least.status.tolist()}")
    landmark_coefficients = least.solution  # [L,R]
C++ / LibTorch
// X:[N,D], targets:[N,R], landmarks:[L,D]
at::NoGradGuard no_grad;
s::PairwiseOptions source;
source.comparison.gamma = 0.3;
auto K = s::kernel(X, c10::nullopt, "rbf", source);
auto A = s::aslinearoperator(K).add_diagonal(0.2);
s::linalg::SolveOptions options;
options.method = "cg";
options.rtol = 1e-6;
auto fit = s::linalg::solve(A, targets, options);
TORCH_CHECK(fit.converged.all().item<bool>(), "CG did not converge");
auto coefficients = fit.solution;  // [N,R]
auto rectangular = s::aslinearoperator(s::kernel(X, landmarks, "rbf", source));
s::linalg::LstsqOptions least_options;  // method defaults to "lsqr".
least_options.damp = 0.2;
least_options.rtol = 1e-6;
auto least = s::linalg::lstsq(rectangular, targets, least_options);
TORCH_CHECK(least.converged.all().item<bool>(), "LSQR did not converge");
auto landmark_coefficients = least.solution;  // [L,R]

Square A [...,N,N] with b [N] or [...,N,R] returns [...,N] or [...,N,R]. SolveResult contains solution, data residual norm, iterations, converged and status. Status meanings remain those of the selected method: code 2 is nonpositive curvature for CG, Lanczos breakdown for MINRES or Arnoldi breakdown for GMRES. Code 0 is convergence, 1 iteration limit, 3 nonfinite arithmetic; CG also uses 4 for an invalid preconditioner. Numerical failure returns diagnostics and the current iterate; inspect them before using it.

Least squares A [...,N,M] returns [...,M] or [...,M,R]. Its LSQRResult also reports normal_residual_norm: a nonzero data residual may be optimal. With damp=0 and a zero start, LSQR targets the minimum-norm solution:

\[ X\approx A^\dagger B,\qquad X\in\operatorname*{argmin}_Z\|AZ-B\|_F,\quad\|X\|_F\ \text{minimal} \]

This is an iterative target, not an SVD rank cutoff. A nonzero initial may retain null-space components. Damping instead minimizes \(\|AX-B\|_F^2+\mathrm{damp}^2\|X\|_F^2\), penalizing the final X. No rank or singular-value estimates are returned. Stopping tolerance and poor conditioning can leave coefficient error. For dense inverse/pseudoinverse matrices use torch.linalg.inv/pinv or ATen; their output still needs storage.

The default square and least-squares modes are inference-only, support compatible compile/export, and exclude CUDA graph capture because native stopping synchronizes the host. The named methods below expose the same numerical implementations. See full unified contracts.

Conjugate Gradient

For real SPD maps, tp.linalg.cg solves kernel ridge systems without storing the generated Gram matrix. Each RHS column has its own recurrence and convergence diagnostics. Symmetry and positive definiteness are caller assumptions.

\[ (K+\alpha I)C=Y,\qquad \widehat Y=KC,\qquad \|Y_{:r}-AC_{:r}\|_2\le\max(\mathrm{atol},\mathrm{rtol}\|Y_{:r}\|_2) \]
Python
# X:[N,D], targets:[N,R]
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.2)
    A = K + 0.1 * tp.eye_like(K)
    fit = tp.linalg.cg(A, targets, rtol=1e-6, max_iter=300)
    if not fit.converged.all():
        raise RuntimeError(f"CG status: {fit.status.tolist()}")
    predictions = K.matmul(fit.solution)  # [N,R]
C++ / LibTorch
// X:[N,D], targets:[N,R]
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.2;
options.chunk_size = 128;
auto K = s::kernel(X, c10::nullopt, "rbf", options);
auto A = s::aslinearoperator(K).add_diagonal(0.1);
s::linalg::CGOptions solve_options;
solve_options.rtol = 1e-6;
solve_options.max_iter = 300;
auto fit = s::linalg::cg(A, targets, solve_options);
// Inspect fit.converged / fit.status before using the result.
auto predictions = K.matmul(fit.solution);  // [N,R]

RHS [N] returns [...,N]; RHS [...,N,R] returns [...,N,R], with broadcast batches and positive R. Diagnostics have shape [...] or [...,R] respectively. CGResult contains solution, float64 residual_norm, int64 iterations and status, and bool converged. Status codes are 0 success, 1 iteration limit, 2 nonpositive curvature, 3 nonfinite arithmetic, 4 nonpositive preconditioned residual energy. The actual residual verifies convergence; numerical failure returns diagnostics. initial broadcasts to the solution, and max_iter=0 only evaluates that guess.

The solver is inference-only; gradient-bearing inputs require explicit no-grad. Native early stopping synchronizes CUDA, so CUDA graph capture is unsupported. Compile/export retain a native solve rather than unrolling iterations. Each matvec still evaluates all pairs; storage includes source tiles, preparation and solution-sized blocks. The kernel ridge example compares with a small dense solve. See full solver contracts.

Diagonal Preconditioning

tp.diag_embed(d) represents a diagonal map from d:[...,N] without allocating [N,N]. tp.linalg.jacobi(d) checks positive finite entries and finite reciprocals, then stores their reciprocals once. Rebuild it if the system changes. General diagonal maps retain the entries and support gradients.

CG accepts preconditioner=M, a fixed SPD inverse action approximating A inverse:

\[ z_k=M r_k,\qquad \rho_k=r_k^Tz_k \]

The original residual still determines convergence. M uses matching [N,N] dimensions/dtype/device and may broadcast to the solution without adding batches. Symmetry and positive definiteness remain caller assumptions; a nonpositive residual energy produces status 4. Gradient-bearing M operands require no-grad.

Python
# X:[N,D], targets:[N,R], d:[N] strictly positive
with torch.no_grad():
    D = tp.diag_embed(d.sqrt())
    K = tp.lazy(X, metric="rbf", gamma=0.2)
    K = K + 2. * tp.eye_like(K)
    A = D @ K @ D  # D(K+2I)D, diagonal = 3*d
    fit = tp.linalg.cg(A, targets, preconditioner=tp.linalg.jacobi(3. * d), rtol=1e-8)
    if not fit.converged.all():
        raise RuntimeError(f"PCG status: {fit.status.tolist()}")
C++ / LibTorch
// X:[N,D], targets:[N,R], d:[N] strictly positive
at::NoGradGuard no_grad;
auto D = s::diagonal(d.sqrt());
s::PairwiseOptions source;
source.comparison.gamma = 0.2;
auto K = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", source)).add_diagonal(2.);
auto A = D.compose(K).compose(D);
s::linalg::CGOptions solve;
solve.preconditioner = s::linalg::jacobi(3. * d);
solve.rtol = 1e-8;
auto fit = s::linalg::cg(A, targets, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "PCG did not converge");

PCG adds one M action and keeps O(NR) vector blocks plus preconditioner state. Fewer steps need not mean lower wall time. A constant-diagonal RBF Gram matrix has no Jacobi iteration benefit; uneven diagonal scaling can benefit. The comparison example reports both paths on one system, with an optional small dense oracle. See full contracts. This option belongs to CG; the other solvers retain their existing interfaces.

Low-Rank and Nyström Preconditioning

tp.linalg.lowrank_inverse(F, shift) prepares the inverse action of \(FF^T+\alpha I\) from stored F:[...,N,L]. For kernel systems, nystrom_inverse(C, W, shift, jitter=0.) uses explicit landmark blocks C=K(X,Z) [...,N,L] and W=K(Z,Z) [...,L,L]:

\[ \widetilde K=C(W+\delta I)^{-1}C^T,\qquad M=(\widetilde K+\alpha I)^{-1} \]

Alpha is shift, delta is jitter. C/W share float32/64 dtype/device with broadcast batches. W must be symmetric, W+delta I positive definite; alpha must be a positive finite scalar with finite reciprocal. Duplicate landmarks commonly need explicit jitter. nystrom_inverse does not choose landmarks or retry with hidden regularization; select them with pivoted Cholesky pivots or streamed farthest-point sampling. Cholesky whitening and a thin SVD form the map without constructing an N-by-N inverse.

Python
# X:[N,D], targets:[N,R], ids:[L] int64 landmark sample indices
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.2)
    rows = torch.arange(X.shape[-2], device=X.device)
    C, W = K.block(rows, ids), K.block(ids, ids)  # stored [N,L], [L,L]
    M = tp.linalg.nystrom_inverse(C, W, 0.03, jitter=1e-8)
    A = K + 0.03 * tp.eye_like(K)
    fit = tp.linalg.cg(A, targets, preconditioner=M, rtol=1e-8)
    if not fit.converged.all():
        raise RuntimeError(f"PCG status: {fit.status.tolist()}")
    # fit.solution:[N,R]; the original full kernel system is solved.
C++ / LibTorch
// X:[N,D], targets:[N,R], ids:[L] int64 landmark sample indices
at::NoGradGuard no_grad;
s::PairwiseOptions source;
source.comparison.gamma = 0.2;
source.chunk_size = 128;
auto K = s::kernel(X, c10::nullopt, "rbf", source);
auto rows = at::arange(X.size(-2), ids.options());
auto C = K.block(rows, ids), W = K.block(ids, ids);
auto M = s::linalg::nystrom_inverse(C, W, 0.03, 1e-8);
auto A = s::aslinearoperator(K).add_diagonal(0.03);
s::linalg::CGOptions solve;
solve.preconditioner = M;
solve.rtol = 1e-8;
auto fit = s::linalg::cg(A, targets, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "PCG did not converge");
// Ordinary features: s::linalg::lowrank_inverse(F, 0.03).

Setup is inference-only and snapshots the inputs. Reuse the map across solves; rebuild after changing the kernel, features, landmarks or shift. Applying the prepared map supports RHS gradients, transpose, composition, native solvers and compile/export. The double basis/spectrum need O(Nr) storage with r=min(N,L), plus scalar shift. Setup stores C/W and temporary double Cholesky/SVD blocks; it does not stream the low-rank factor. With L near N, storage becomes quadratic.

For a thin spectral basis, the action is:

\[ MB=\frac{B-U(U^TB)}{\alpha} + U\operatorname{diag}\!\left((\sigma_k^2+\alpha)^{-1}\right)U^TB \]

Double arithmetic reduces cancellation before casting back to the source dtype; very small shifts can still amplify roundoff. There is no silent rank cutoff. A preconditioner adds work to every iteration and has a setup cost, so compare end-to-end time and memory as well as iteration counts. CG still solves the full original kernel system, using the approximate inverse only to choose search directions. The Nyström example compares plain CG, Jacobi and Nyström, reporting setup separately and checking held-out predictions against a dense reference. See the full contracts.

Symmetric Indefinite Systems

tp.linalg.minres solves real symmetric square maps that may be indefinite, such as block KKT systems. Symmetry is a caller assumption, not an exhaustive runtime check. A short Lanczos/QR recurrence retains O(NR) vector blocks per batch rather than a full search basis. No preconditioner or MINRES-QLP variant is provided.

The shape conventions match CG: A [...,N,N], b [N] or [...,N,R], with broadcast batches, independent RHS convergence and an optional broadcast initial guess. max_iter=0 evaluates that guess; the budget counts all Lanczos steps. Actual \(\|b-Ax\|_2\le\max(\mathrm{atol},\mathrm{rtol}\|b\|_2)\) verifies convergence on the returned-precision solution. A false recurrence estimate may restart unfinished columns from their actual residual.

MINRESResult returns solution, float64 residual_norm, int64 iterations and status, and bool converged. Status 0 is convergence, 1 iteration limit, 2 Lanczos/QR breakdown without verified convergence, 3 nonfinite arithmetic. Singular/inconsistent maps have no minimum-norm or least-squares guarantee; use LSQR for that objective. Failure retains the current iterate.

Internal vectors/scalars use float64, while source products and returned solutions retain supplied float32/float64 precision. Native execution is inference-only, with compile/export support but no CUDA graph capture. See MINRES contracts and the KKT gallery example.

Nonsymmetric Systems

tp.linalg.gmres solves real square systems without CG's symmetry/PSD assumptions. It uses a restarted Arnoldi basis, two modified Gram-Schmidt passes and Givens rotations. restart bounds the basis width (default 20, clamped to N); max_iter counts total Arnoldi steps per batch/RHS, not restart cycles.

Convergence checks the actual \(\|b-Ax\|_2\le\max(\mathrm{atol},\mathrm{rtol}\|b\|_2)\). Map/RHS batches broadcast, with b [N] or [...,N,R] and matching solution shape. initial broadcasts without adding batches. max_iter=0 evaluates it. GMRESResult returns solution, float64 residual_norm, int64 iterations and status, and bool converged. Status 0 is convergence, 1 total iteration limit, 2 Arnoldi/triangular breakdown without verified convergence, 3 nonfinite arithmetic. Failure retains the current iterate; inspect diagnostics.

The solver is inference-only and supports compile/export, with no CUDA graph capture or preconditioner. Source products/output use float32/float64 as supplied; basis, orthogonalization and small QR blocks use float64. Effective restart width m gives O(NRm+Rm²) workspace per batch plus source/consumer workspace. Restarts may stagnate, and singular systems have no least-squares/minimum-norm guarantee. See personalized PageRank for Python/C++ code and the full GMRES contract.

Rectangular Least Squares

tp.linalg.lsqr(A, B, damp=0.2) minimizes \(\|AX-B\|_F^2+0.2^2\|X\|_F^2\) for real rectangular maps. It uses A and A.T actions without constructing A.T A; no symmetry or PSD assumption is required. A has shape [...,N,M], B is [N] or [...,N,R], and the solution is [...,M] or [...,M,R], with broadcast batches.

Convergence uses the actual normal residual \(G=A^\mathsf T(B-AX)-\mathrm{damp}^2X\):

\[ \|G_{:r}\|_2\le\max(\mathrm{atol},\mathrm{rtol}\|(A^\mathsf T B)_{:r}\|_2) \]

LSQRResult returns solution, float64 residual_norm (data residual) and normal_residual_norm (stationarity), int64 iterations/status and bool converged. A nonzero data residual may be optimal. Status 0 is convergence, 1 iteration limit, 2 recurrence breakdown without verified convergence, and 3 nonfinite arithmetic. Check diagnostics before using the result. Damping always penalizes the final solution, including with a nonzero initial.

The solver is inference-only, with float32/float64 source products and solution blocks, and double scalar recurrences. It supports native compile/export but not CUDA graph capture. It retains O((N+M)R) solver blocks plus source workspace. The landmark regression gallery example shows Python/C++ calls, held-out prediction and an independent dense check. See full LSQR contracts.

Differentiable Square Solves

solve(..., differentiable=True) differentiates a converged nonsingular system without retaining its iteration history. For incoming gradient G:

\[ AX=B,\qquad A^\mathsf T\Lambda=G,\qquad \overline B=\Lambda,\qquad \overline A=-\Lambda X^\mathsf T \]

The matrix cotangent is contracted through streamed source products and map factors. Retained state includes operands, the fitted solution and narrow replay blocks; a streamed source needs no dense gradient matrix.

Python
# X:[N,D], targets:[N,R], grad-enabled tensors
A = tp.lazy(X, metric="rbf", gamma=0.2)
A = A + 0.1 * tp.eye_like(A)
fit = tp.linalg.solve(A, targets, method="cg", differentiable=True, rtol=1e-7)
fit.solution.square().mean().backward()
C++ / LibTorch
// X:[N,D], targets:[N,R]; use a grad-enabled scope.
s::PairwiseOptions source;
source.comparison.gamma = 0.2;
auto A = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", source)).add_diagonal(0.1);
s::linalg::SolveOptions options;
options.method = "cg";
options.differentiable = true;
options.rtol = 1e-7;
auto fit = s::linalg::solve(A, targets, options);
fit.solution.square().mean().backward();

The selected CG/MINRES/GMRES method also solves the adjoint, using the same stopping options. Both solves must converge; failure raises. Only the solution has first gradients; diagnostics are detached. Initial guesses must be detached, CG supports a fixed SPD inverse-action preconditioner in both solves, with no backward edge through its algorithmic-only operands. Shared system operands still receive their mathematical gradients. Higher derivatives remain unsupported. Symmetry assumptions still apply, and poor conditioning or loose tolerances can leave inaccurate gradients. Named solvers remain inference-only. lstsq(..., damp=0.2, differentiable=True) adds implicit gradients for uniquely regularized rectangular least squares; see below. See full contracts.

Differentiable Damped Least Squares

Positive damping gives a unique solution even for wide or rank-deficient maps. For fixed damping alpha, residual R and solution cotangent G:

\[ (A^\mathsf T A+\alpha^2I)\Lambda=G,\qquad \overline B=A\Lambda,\qquad \overline A=R\Lambda^\mathsf T-(A\Lambda)X^\mathsf T,\qquad R=B-AX \]
Python
# X:[N,D], landmarks:[L,D], targets:[N,R]; grad-enabled operands
A = tp.lazy(X, landmarks, metric="rbf", gamma=0.3)
fit = tp.linalg.lstsq(A, targets, damp=0.2, differentiable=True, rtol=1e-7)
fit.solution.square().mean().backward()
C++ / LibTorch
// X:[N,D], landmarks:[L,D], targets:[N,R]; grad-enabled operands
s::PairwiseOptions source;
source.comparison.gamma = 0.3;
auto A = s::aslinearoperator(s::kernel(X, landmarks, "rbf", source));
s::linalg::LstsqOptions options;
options.damp = 0.2;
options.differentiable = true;
options.rtol = 1e-7;
auto fit = s::linalg::lstsq(A, targets, options);
fit.solution.square().mean().backward();

Forward uses LSQR, backward CG on the damped normal action and streamed product VJPs. No normal matrix, dense matrix cotangent or iteration history is stored for a streamed source. Both solves must converge or raise. Only first gradients through source inputs/factors and RHS are supported; damping is a fixed scalar, initial guesses must be detached. Damping and its square must be finite and strictly positive in the source dtype. Small damping/poor conditioning can amplify errors; the backward normal action squares singular values. See full contracts.

Group Projection and Kernel LDA

Group projection replaces each sample value by the mean of its group. Labels are int64 [...,N], with arbitrary signed IDs and broadcast batches. The map snapshots group membership, stores IDs/counts, and supports value gradients. like supplies only float32/float64 dtype and device.

\[ P=E\operatorname{diag}(n_c^{-1})E^\mathsf T,\qquad (PV)_i=\frac{1}{n_{g_i}}\sum_{j:g_j=g_i}V_j,\qquad P^\mathsf T=P,\quad P^2=P \]
Python
# labels:[N], X:[N,D], V:[N,R]
P = tp.group_projection(labels, like=X)  # logical [N,N]
means = P.matmul(V)                      # [N,R], repeated group means
K = tp.lazy(X, metric="rbf", gamma=0.2)
R = K - P @ K  # (I-P)K
Sw = R.mT @ R
Sw = Sw + 0.1 * tp.eye_like(Sw)  # K(I-P)K + 0.1 I
C++ / LibTorch
// labels:[N], X:[N,D], V:[N,R]
auto P = s::group_projection(labels, X);
auto means = P.matmul(V);
s::PairwiseOptions options;
options.comparison.gamma = 0.2;
auto K = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", options));
auto R = K - P.compose(K);
auto Sw = R.transpose().compose(R).add_diagonal(0.1);

For symmetric K, Sw is the coefficient-regularized within-class scatter map. The kernel LDA example solves against a class block F [N,C] with native CG, then diagonalizes F.T @ Sw^{-1}F [C,C]. It retains at most C-1 positive discriminant directions. No dense [N,N] array is needed in that path. The example checks a small dense generalized eigenproblem, including eigenvalues and scatter orthogonality.

Group preparation sorts labels once, roughly O(N log N). Each action uses scatter/gather with O(NR) work/workspace per broadcast partition. The map stores two [N] integer arrays, not an [N,N] projector. The LDA class blocks are separate [N,C] allocations; the advantage decreases as C approaches N. See partition contracts and LDA formulas and dimensions.

Truncated Kernel PCA

Ordinary PCA covariance and LDA scatters are [d,d] for X [N,d]. With a nonlinear kernel, the implicit feature dimension can be infinite, and exact dual formulations use [N,N] matrices. A small input d does not reduce that dual decomposition size.

An iterative approximation can instead build a basis Q [N,r], using only streamed centered products, then decompose a small projected matrix:

\[ H=I-\mathbf1\mathbf1^\mathsf{T}/N,\qquad K_cV=H\bigl(K(HV)\bigr),\qquad B=Q^\mathsf{T}K_cQ \]

The kernel PCA example uses native tp.linalg.eigh, with QR-stabilized block subspace iteration and Rayleigh-Ritz projection. It returns training coordinates [N,k], after checking residuals and convergence. Each pass still evaluates all sample pairs; storage includes [N,r] blocks, [r,r] projections and kernel tile workspace. The no-grad example assumes a symmetric PSD kernel. Its optional dense reference compares spectra/subspaces and checks linear and zero-rank cases. Held-out projection retains the training kernel column means and applies C.centered(reference_mean) to the rectangular test-to-training map. The same overload is available in C++. It preserves query-batch independence and supports first gradients in map actions; the eigensolve remains inference-only.

Python
# X:[N,D], N>=12
with torch.no_grad():
    A = tp.lazy(X, metric="rbf", gamma=0.2).centered()
    fit = tp.linalg.eigh(A, k=4, subspace_size=12, max_iter=100,
                       rtol=1e-6, atol=1e-10)
    if not fit.converged.all():
        raise RuntimeError(f"Eigen status: {fit.status.tolist()}")
    scores = fit.eigenvectors * fit.eigenvalues.clamp_min(0).sqrt()  # [N,4]
C++ / LibTorch
// X:[N,D], N>=12
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.2;
auto A = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", options)).centered();
s::linalg::EighOptions eigen_options;
eigen_options.k = 4;
eigen_options.subspace_size = 12;
eigen_options.max_iter = 100;
eigen_options.rtol = 1e-6;
eigen_options.atol = 1e-10;
auto fit = s::linalg::eigh(A, eigen_options);
// Inspect fit.converged / fit.status before using the result.
auto scores = fit.eigenvectors * fit.eigenvalues.clamp_min(0).sqrt().unsqueeze(-2);

EigenResult contains descending eigenvalues [...,k], eigenvectors [...,N,k], and residual_norm, iterations, converged, status, each [...,k]. Residual norms are float64; counts/status int64 and convergence bool. Each batch stops independently after all requested ranks converge. The criterion checks the actual source action and vector orthogonality:

\[ \|Au_i-\lambda_i u_i\|_2\le\max(\mathrm{atol},\mathrm{rtol}|\lambda_i|),\qquad U^\mathsf TU\approx I \]

The width r defaults to min(N,2*k+8) or initial.shape[-1]. Require 1<=k<=r<=N. initial must have full numerical column rank, shape [...,N,r], and broadcast to the map's batches without adding batches. Otherwise the native solver draws a random start from the current PyTorch RNG state. max_iter=0 evaluates the starting subspace without updates. The iteration count repeats the batch's number of subspace updates across its k sorted ranks; individual vectors are not locked and can rotate within repeated eigenspaces.

StatusMeaning
0Residual and orthogonality checks pass
1Iteration limit reached
2Rank-deficient initial block
3Nonfinite input/action/arithmetic
4Significantly negative projected direction
5Loss of orthogonality

Numerical failures return diagnostics; malformed options raise. Status 2-5 fails the batch with NaN values/vectors/residuals. Iteration exhaustion retains finite estimates. The solver does not clip eigenvalues; PCA clips tiny roundoff-negative variance only when forming scores. Native early stopping synchronizes CUDA, so CUDA graph capture is unsupported. Compatible compile/export retains a native solve. Gradient-bearing sources/initial blocks need an explicit no-grad scope.

There is no smallest/indefinite eigenvalue mode, and PSD detection is not exhaustive. Small residuals do not certify that a caller-supplied invariant starting subspace did not miss larger eigenvalues. Use random starts, sufficient width and more updates for flat spectra. A positive atol helps null directions; negative Ritz values within max(atol,roundoff_bound) are tolerated. With r near N the retained basis itself approaches dense storage. See solver contracts and scatter formulas and dimensions.

Rectangular Truncated SVD

For a rectangular scalar map A [N,M], use tp.linalg.svd to estimate leading singular triplets. Two-sided QR-stabilized iteration applies A and A.T, then decomposes an [r,r] projection. It never forms A.T A.

\[ AV_k\approx U_k\Sigma_k,\qquad A^\mathsf T U_k\approx V_k\Sigma_k,\qquad A_k=U_k\Sigma_kV_k^\mathsf T \]
Python
# X:[N,D], Y:[M,D], min(N,M)>=12; float32/64 inference inputs
A = tp.lazy(X, Y, metric="rbf", gamma=0.2)
fit = tp.linalg.svd(A, k=4, subspace_size=12, rtol=1e-6, atol=1e-9)
if not fit.converged.all():
    raise RuntimeError(f"SVD status: {fit.status.tolist()}")
# B:[M,R]. Reuse fitted factors without evaluating more source pairs.
approximation = fit.left_vectors @ (fit.singular_values[:, None] * (fit.right_vectors.T @ B))
C++ / LibTorch
// X:[N,D], Y:[M,D], min(N,M)>=12; float32/64 inference inputs
s::PairwiseOptions source;
source.comparison.gamma = 0.2;
auto A = s::aslinearoperator(s::kernel(X, Y, "rbf", source));
s::linalg::SVDOptions options;
options.k = 4;
options.subspace_size = 12;
options.rtol = 1e-6;
options.atol = 1e-9;
auto fit = s::linalg::svd(A, options);
TORCH_CHECK(fit.converged.all().item<bool>(), "SVD did not converge");
auto approximation = at::matmul(fit.left_vectors,
    fit.singular_values.unsqueeze(-1) *
    at::matmul(fit.right_vectors.transpose(-1,-2), B));  // [N,R]

The approximation code is unbatched. Results contain descending singular_values:[...,k], left_vectors:[...,N,k], right_vectors:[...,M,k] (columns V, not Vh), and per-triplet residual_norm, iterations, converged, status. Residuals report the maximum of the two singular-equation errors; orthogonality is also checked. Require 1<=k<=r<=min(N,M). The default width is min(N,M,2*k+8) or initial.shape[-1]; initial is a full-rank right block [...,M,r], otherwise a random start is drawn from the current PyTorch RNG. Initial batches may broadcast to the map but cannot add batches.

Retained numerical state is O((N+M)*r+r*r) double blocks plus source inputs, preparation, tiles and outputs. Each pass still evaluates source pairs. After fitting, factor products evaluate the approximate map without further pairs. Width near min(N,M) approaches dense storage. Finite rank collapse and zero maps are valid; use positive atol for null triplets. Flat spectra may need wider subspaces or more updates, and small residuals alone do not certify global leading order if the initial subspace misses a leading direction.

max_iter counts updates after the initial two-sided extraction; zero checks only that extraction. Each batch stops when all requested triplets converge. Status 0 converged, 1 iteration limit (finite estimates), 2 rank-deficient start, 3 nonfinite action/arithmetic, 4 loss of orthogonality. Status 2-4 returns NaN estimates for the failed batch. Invalid arguments raise. This leading-only solver is inference-only and excludes CUDA graph capture; compatible compile/export retains a native operator. See full contracts and the runnable approximation example.

Alignment and Independence

For square Gram sources over the same N observations, let \(H=I-\mathbf1\mathbf1^\mathsf{T}/N\).

\[ \operatorname{CKA}(K,L)=\frac{\langle HKH,HLH\rangle_F}{\|HKH\|_F\|HLH\|_F},\qquad \operatorname{HSIC}_{b}=\frac{\langle HKH,HLH\rangle_F}{(N-1)^2}\]

Use frobenius_inner_product(...,center=True,normalize=True) for centered alignment and hsic for biased or unbiased HSIC. Zero alignment norms give zero values and first gradients. Biased HSIC requires N≥2; unbiased N≥4. See full alignment conventions.

Geometric Interactions

Vector kernels additionally support weighted displacement and residual outer products. For weights Kij, these aggregate terms such as \(\sum_jK_{ij}(y_j-x_i)\) and \(\sum_jK_{ij}(y_j-x_i)(y_j-x_i)^\mathsf{T}\) without storing all N×M vectors or matrices. Normalized and centered variants have explicit contracts. Finite-iteration Sinkhorn maps instead use transport-plan conditional means. See kernel geometry and transport maps.

Quadratic Optimization and Kernel SVM

tp.optimize.box_qp minimizes a real symmetric convex quadratic within finite box bounds, optionally with one affine equality. It uses native source products and projected steps; a generated [N,N] matrix is not retained.

\[ \min_x\ \tfrac12x^\mathsf TQx+q^\mathsf Tx,\qquad \ell\le x\le u,\qquad a^\mathsf Tx=b \]

For a binary C-SVM, use Q=diag(y) K diag(y), q=-ones, bounds [0,C] and y.T @ alpha=0. The equality is needed for a free intercept.

Python
# X:[N,D], labels:[N] floats {-1,+1}, both classes present
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.5)
    Q = K.weight_rows(labels).weight_columns(labels)
    fit = tp.optimize.box_qp(Q, -torch.ones_like(labels), lower=0., upper=1.,
                            equality=(labels, 0.), rtol=1e-6, max_iter=1500)
    if not fit.converged:
        raise RuntimeError(f"QP status: {fit.status.item()}")
    raw_scores = K.matmul(fit.solution * labels)  # [N], before intercept
C++ / LibTorch
// X:[N,D], labels:[N] floats {-1,+1}
at::NoGradGuard no_grad;
s::PairwiseOptions kernel_options;
kernel_options.comparison.gamma = 0.5;
auto K = s::kernel(X, c10::nullopt, "rbf", kernel_options);
auto Q = s::aslinearoperator(K.weight_rows(labels).weight_columns(labels));
s::optimize::BoxQPOptions options;
options.lower = at::scalar_tensor(0., X.options());
options.upper = at::scalar_tensor(1., X.options());
options.equality = s::optimize::QPEquality{
    labels, at::scalar_tensor(0., X.options())};
options.rtol = 1e-6;
options.max_iter = 1500;
auto fit = s::optimize::box_qp(Q, -at::ones_like(labels), options);
// Inspect fit.converged / fit.status before using coefficients.
auto raw_scores = K.matmul(fit.solution * labels);

q [N] returns solution [...,N]; q [...,N,R] defines independent objectives and returns [...,N,R]. Q/q batches broadcast; use [...,N,1] for batched vectors. Bounds, equality vectors and initial guesses broadcast to the solution without adding batches; [N] bounds/vectors mean samples. Equality targets broadcast to [...,R]. Numeric bounds/targets become tensors matching q; Tensor arguments already need the same float32/float64 dtype and device. Bounds must be finite. max_iter=0 projects/evaluates the initial guess, defaulting to zero.

QPResult exposes solution, objective, iterations, converged, status, optimality and feasibility. Diagnostics shape is [...] for vector q or [...,R] for block q. Objective/residuals are float64; status/counts int64 and convergence bool. Each batch/RHS stops independently. Optimality is the infinity norm of the projected-gradient mapping; feasibility covers box violations and the normalized equality gap. The native method uses safeguarded Newton/bisection for equality projection and a quadratic line minimization along feasible segments.

StatusMeaning
0Optimality and feasibility checks pass
1Iteration limit
2Infeasible bounds/equality
3Nonfinite input/action/arithmetic
4Negative search curvature
5Projection failed numerically
6No representable progress before convergence

Malformed arguments raise. Iteration exhaustion retains finite approximations; status 2-6 returns NaN solution/objective/residuals. Symmetry/PSD are assumptions: negative curvature detection is not an exhaustive convexity test. Inference-only execution requires explicit no-grad for gradient-bearing inputs. Compatible compile/export retains one native solve; CUDA graph capture is unsupported. Vector-sized solver/constraint blocks and source tiles still consume storage; repeated products/projections can be slower than specialized dense solvers.

The binary kernel SVM example recovers an intercept from free supports or its KKT interval, reports a primal-dual gap, and optionally compares the same problem with a dense matrix. See complete QP contracts.

Entropic Transport

tp.optimize.sinkhorn solves entropic optimal transport for any scalar lazy cost: a named metric, an entrywise expression graph, a make_pairwise comparison or a dense matrix wrapped with tp.as_lazy. Masses a:[...,N] and b:[...,M] may be nonuniform. Balanced problems need equal totals; masses are never silently normalized. The solver returns log-domain potentials f and g, and the coupling is never stored:

\[ \pi_{ij}=a_ib_j\exp\bigl((f_i+g_j-C_{ij})/\varepsilon\bigr) \]
Python
# x:[N,D], y:[M,D], a:[N], b:[M] masses with equal totals
C = tp.lazy(x, y, metric="sqeuclidean", chunk_size=64)
fit = tp.optimize.sinkhorn(C, a, b, epsilon=0.1)
if not fit.converged.all():
    raise RuntimeError(f"Sinkhorn status: {fit.status.tolist()}")
plan = tp.optimize.sinkhorn_plan(C, fit, a, b, epsilon=0.1)  # lazy [N,M]
row_mass = plan.sum(-1)                                      # [N]
mapped = (plan @ y) / row_mass.unsqueeze(-1)                 # [N,D]
C++ / LibTorch
namespace tp = torchpairwise::ops;
// x:[N,D], y:[M,D], a:[N], b:[M] masses with equal totals
tp::PairwiseOperationOptions source;
source.chunk_size = 64;
auto C = tp::pairwise_expression(x, y, "sqeuclidean", false, source);
tp::SinkhornOptions options;
options.epsilon = 0.1;
auto fit = tp::sinkhorn(C, a, b, options);
TORCH_CHECK(fit.converged.all().item<bool>(), "Sinkhorn did not converge");
auto plan = tp::sinkhorn_plan(C, fit, a, b, options.epsilon);  // lazy [N,M]
auto row_mass = plan.reduce("sum", -1);                        // [N]
auto mapped = plan.matmul(y) / row_mass.unsqueeze(-1);         // [N,D]

The plan stays lazy, so its sums and products stream cost tiles. The map is defined only for rows with positive transported mass. Pass the same cost, masses and epsilon to sinkhorn_plan that were used for the solve; the result does not store them. epsilon has the units of the cost and must be positive. Check converged before using potentials: max_iter, rtol, atol and check_every control stopping, initial=(f, g) warm-starts, epsilon_scaling anneals small regularization and reach=rho replaces the marginal constraints by KL penalties for unbalanced problems.

tp.optimize.sinkhorn_divergence combines three solves into \(\mathrm{OT}(a,b)-\tfrac12\mathrm{OT}(a,a)-\tfrac12\mathrm{OT}(b,b)\) and is available in Python only:

C_xx = tp.lazy(x, metric="sqeuclidean", chunk_size=64)
C_yy = tp.lazy(y, metric="sqeuclidean", chunk_size=64)
loss = tp.optimize.sinkhorn_divergence(C, C_xx, C_yy, a, b, epsilon=0.1)

Gradients use the envelope derivative of the converged dual: the cost derivative is the coupling, and it reaches metric inputs, parameters and masses. The iterations and returned potentials are not differentiated. The potentials and objective use float64. Each half-step still visits every pair. For point-set layouts and the fixed-cost catalog entry, see Sinkhorn divergence. The entropic transport notes give the objectives, unbalanced duals and gradient conventions.

Traces and Matrix Functions

The spectral consumers provide exact/Hutchinson traces, exact/SLQ SPD log-determinants, symmetric exponential actions and SPD inverse-square-root actions, plus Gaussian process predictive variance and landmark selection. They consume the same maps and expose Python/C++ APIs with explicit probe counts, randomness and Krylov budgets. Trace/logdet also expose opt-in first estimate gradients; SLQ selects its solve-based derivative explicitly. The gallery example combines them for graph diffusion, whitening and kernel log-determinants.

Streamed operators

Traces, Log-Determinants and Matrix Functions

tp.linalg consumes a square real linear map through its products. Python accepts a lazy pairwise object or dense Tensor. C++ accepts a LinearOperator through the streaming::linalg namespace.

All consumers use CPU/CUDA float32/float64. Trace and log-determinant estimates support opt-in first gradients through differentiable=True; the default and matrix-function actions require explicit torch.no_grad() / at::NoGradGuard for gradient-bearing inputs. Compatible torch.compile and torch.export graphs retain native forward/backward consumers. Runtime numerical checks and breakdown use synchronization, so CUDA graph capture is unsupported. Symmetry and positive definiteness are caller assumptions; observed failures raise, but finite probes cannot establish global SPD.

Explicit Method Selection

EntryMethod / AssumptionResultFull Matrix Required
trace(A, method="exact")Any square real AExact trace, up to roundoffNo
trace(A, method="hutchinson", ...)Any square real AStochastic traceNo
logdet(A, method="exact")Symmetric positive definite ADouble Cholesky log-determinantYes, explicitly selected
logdet(A, method="slq", ...)Symmetric positive definite AStochastic Lanczos quadratureNo
expm_multiply(A, V, scale=...)Symmetric real AApproximate exponential actionNo
inv_sqrt_multiply(A, V)Symmetric positive definite AApproximate inverse square-root actionNo

No hidden dense fallback, jitter, eigenvalue clipping or solver selection is performed. A generated comparison map need not be symmetric or SPD; choose a metric/kernel and algebra that give the required mathematical properties.

Trace and Random Probes

For independent probes with \(\mathbb E[z_pz_p^\mathsf T]=I\),

\[ \widehat{\operatorname{tr}}(A)=\frac1P\sum_{p=1}^Pz_p^\mathsf TAz_p \]
Python
import torch
import torchpairwise as tp

# X:[N,D], float32/64
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=.2)
    A = K + .1 * tp.eye_like(K)
    exact = tp.linalg.trace(A, method="exact", block_size=16)
    estimate = tp.linalg.trace(A, method="hutchinson", num_probes=64,
                              seed=17, block_size=16)
    value = estimate.estimate                 # [...] float64
    sampling_error = estimate.standard_error  # [...] float64
    samples = estimate.samples                # [...,64] float64
C++ / LibTorch
#include <torchpairwise.h>
namespace s = torchpairwise::streaming;
at::NoGradGuard no_grad;
s::PairwiseOptions source;
source.comparison.gamma = .2;
auto A = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", source))
             .add_diagonal(.1);
s::linalg::TraceOptions options;
options.method = "hutchinson";
options.num_probes = 64;
options.seed = 17;
options.block_size = 16;
auto estimate = s::linalg::trace(A, options);

Stochastic methods require a positive integer num_probes, or explicit probes of shape [...,N,P]. Internal probes are Rademacher (+1/-1). Supplied probes match A's dtype/device and may broadcast to its existing batch dimensions, without adding batches; supplying probes forbids num_probes and seed. Arbitrary supplied vectors do not automatically have the unbiased stochastic interpretation. For example, a complete basis scaled by \(\sqrt N\) gives an exact trace average, but is not a set of independent random probes.

A nonnegative seed uses a private device generator, leaving the global RNG state unchanged. Omitting seed consumes PyTorch's device RNG. Fixed seed, shape, device and blocking reproduce draws; changing block_size or device can change probes. Reuse explicit probes for common random numbers across models. Exact methods reject stochastic options.

TraceResult has estimate, standard_error and samples. The first two have A's batch shape; samples adds a final probe axis. Standard error is sample standard deviation divided by \(\sqrt P\), NaN for P=1. It is not a confidence bound. Exact returns zero standard error and an empty final sample axis.

Exact trace uses a direct source diagonal when available and otherwise applies blocks of coordinate vectors. It does not require a full N-by-N identity or matrix. With block_size approaching N, a coordinate block and its product can themselves approach quadratic storage. block_size accepts any positive integer and bounds concurrent applied columns. Generated source tiles have their own chunk_size bound.

SPD Log-Determinants

For SPD A, \(\log\det A=\operatorname{tr}(\log A)\). Lanczos starts at \(q_1=z/\|z\|\) and constructs a small symmetric tridiagonal projection T:

\[ z^\mathsf T\log(A)z\approx\|z\|^2e_1^\mathsf T\log(T)e_1 \]
Python
with torch.no_grad():
    result = tp.linalg.logdet(A, method="slq", num_probes=64,
                              seed=17, max_iter=32, block_size=8)
    log_determinant = result.estimate  # [...] float64
    probe_steps = result.iterations    # [...,64] int64
    # Small problems only: explicitly materializes A for a Cholesky reference.
    exact = tp.linalg.logdet(A, method="exact", block_size=16)
C++ / LibTorch
s::linalg::LogdetOptions options;
options.method = "slq";
options.num_probes = 64;
options.seed = 17;
options.max_iter = 32;
options.block_size = 8;
auto result = s::linalg::logdet(A, options);

LogdetResult adds per-probe iterations to the trace result. SLQ applies independent, fully reorthogonalized Lanczos recurrences per probe, with positive max_iter (default 32, capped by N). Numerical invariant-subspace breakdown ends a column early. Observed nonpositive Ritz values raise.

There are two different approximations: finite random probes and finite Lanczos depth. standard_error describes only probe variation. Increase max_iter separately to check quadrature stability; increasing probe count does not remove quadrature bias. Exact methods reject max_iter and all stochastic options. Exact checks symmetry within roundoff and SPD by Cholesky; it uses O(N²) storage and is intended for an explicitly chosen dense reference.

Matrix-Function Actions

For symmetric A and a column v, Lanczos approximates the action directly:

\[ f(A)v\approx\|v\|Q_k f(T_k)e_1 \]

Only the small projected matrix function is computed. scale in the exponential means \(f(\lambda)=\exp(\mathrm{scale}\,\lambda)\); for whitening, \(f(\lambda)=\lambda^{-1/2}\).

Python
# L:[N,N] symmetric graph Laplacian as a lazy pairwise object; V:[N,R]
with torch.no_grad():
    diffusion = tp.linalg.expm_multiply(L, V, scale=-.5, max_iter=32)
    diffused = diffusion.action  # [N,R], same dtype as V
    whitened = tp.linalg.inv_sqrt_multiply(A, V, max_iter=32).action
C++ / LibTorch
s::linalg::MatrixFunctionOptions options;
options.max_iter = 32;
options.block_size = 8;
auto diffusion = s::linalg::expm_multiply(L, V, -.5, options);
auto whitened = s::linalg::inv_sqrt_multiply(A, V, options).action;

V can have shape [N] or [...,N,R]; A/V batches broadcast. Use [...,N,1] for batched vectors. Each RHS column has its own recurrence and steps. The returned MatrixFunctionResult contains action, error_estimate and iterations. Diagnostics have shape [...] for a vector RHS or [...,R] for blocks; errors are float64 and counts int64.

error_estimate is the norm of the difference of the final two Krylov actions, a heuristic, not a certified bound. It is infinity after one unfinished step, zero for zero RHS or numerical invariant-subspace breakdown. Small successive differences can miss approximation error; compare larger max_iter, especially for ill-conditioned inverse roots. The budget is fixed; no tolerance or automatic convergence guarantee is implied. Exponentials require symmetry, including indefinite symmetric A; general nonsymmetric matrices are unsupported.

Estimate Gradients

Set differentiable=True to differentiate result.estimate with respect to operator inputs and tensor factors. This supports first derivatives in Python and C++, including compatible compiled/exported forward and backward graphs. standard_error, samples and iterations are fixed diagnostics. Probes are algorithmic inputs; detach trainable probes explicitly. Supplied probes are snapshotted by value, so later edits do not change backward's random vectors.

For exact trace, blocked product VJPs differentiate the diagonal sum. For Hutchinson, they differentiate the returned finite-probe estimate:

\[ d\widehat{\operatorname{tr}}(A)=\frac1P\sum_pz_p^\mathsf T(dA)z_p \]

Exact log-determinants use blocked Cholesky inverse actions:

\[ d\log\det A=\operatorname{tr}(A^{-1}dA) \]

SLQ requires an explicit gradient choice. gradient="solve" uses the same forward probes, solves \(A\lambda_p=z_p\) with CG, then applies product VJPs:

\[ \widehat{d\log\det A}=\frac1P\sum_p\lambda_p^\mathsf T(dA)z_p \]

This estimates the derivative of the underlying log-determinant. It is not the derivative of the finite-depth SLQ forward approximation. With independent isotropic probes and exact solves it is unbiased; finite solves introduce numerical error. Even an exact solve does not remove forward quadrature bias or finite-probe variation. Finite-SLQ algorithm differentiation is not implemented.

Python
# Operator inputs/factors can require gradients.
trace = tp.linalg.trace(A, method="hutchinson", num_probes=32, seed=17,
                       differentiable=True)
exact = tp.linalg.logdet(A, method="exact", differentiable=True)
slq = tp.linalg.logdet(A, method="slq", num_probes=32, seed=17,
    max_iter=24, differentiable=True, gradient="solve",
    solve_rtol=1e-8, solve_max_iter=500)
slq.estimate.backward()
C++ / LibTorch
s::linalg::TraceOptions trace_options;
trace_options.method = "hutchinson";
trace_options.num_probes = 32;
trace_options.seed = 17;
trace_options.differentiable = true;
auto trace = s::linalg::trace(A, trace_options);
s::linalg::LogdetOptions options;
options.method = "slq";
options.num_probes = 32;
options.seed = 17;
options.max_iter = 24;
options.differentiable = true;
options.gradient = "solve";
options.solve_rtol = 1e-8;
options.solve_max_iter = 500;
auto slq = s::linalg::logdet(A, options);
slq.estimate.backward();

solve_rtol, solve_atol, solve_max_iter control backward CG only, defaulting to 1e-5, 0 and 1000. Every backward solve must converge or an error is raised. They are accepted only for differentiable SLQ; max_iter remains the separate forward Lanczos budget. Exact methods do not need these options. No backward preconditioner is inferred. Higher derivatives are unsupported.

Stochastic differentiation stores a fixed [...,N,P] probe snapshot and source operands, then replays products in block_size columns. Backward consumes no new random numbers and stores no CG/Lanczos iteration history. Prepared source statistics can be reused within replay. Exact logdet backward recomputes a dense Cholesky factor; exact trace/Hutchinson and SLQ backward use blocked actions. Their source chunk, RHS block and probe budgets determine workspace. The usual version checks detect modified operator operands between forward and backward. See the kernel likelihood example for bandwidth/noise learning and separate dense gradient checks.

Gaussian Process Prediction

For a Gaussian process with kernel \(K\) and noise variance \(\sigma^2\), the posterior variance at test inputs needs \(A^{-1}\) for \(A=K+\sigma^2I\). tp.linalg.love_cache prepares an inference-only low-rank inverse root from native Lanczos: with orthonormal basis \(Q\) and projected tridiagonal \(T=LL^\mathsf T\), the cache stores \(R=QL^{-\mathsf T}\) (cache.root), so \(A^{-1}\approx RR^\mathsf T\) and no N-by-N matrix is formed. predictive_variance then streams the rectangular test-to-training kernel:

\[ v_i\approx k(x_i,x_i)-\sum_r\bigl[K_{*X}R\bigr]_{ir}^2 \]
Python
# X:[N,D], targets:[N], test:[T,D]; noise is a positive variance.
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=.2)
    A = K + noise * tp.eye_like(K)
    cache = tp.linalg.love_cache(A, rank=32, initial=targets)  # root:[N,32]
    cross = tp.lazy(test, X, metric="rbf", gamma=.2)
    prior = tp.lazy(test, metric="rbf", gamma=.2).diagonal()       # [T]
    variance = tp.linalg.predictive_variance(cache, cross, prior)  # [T]
C++ / LibTorch
namespace tp = torchpairwise::ops;
// X:[N,D], targets:[N], test:[T,D]
at::NoGradGuard no_grad;
tp::PairwiseOperationOptions source;
source.comparison.gamma = .2;
auto K = tp::pairwise_expression(X, c10::nullopt, "rbf", true, source);
auto A = tp::aslinearoperator(K).add_diagonal(noise);
auto cache = tp::love_cache(A, 32, targets);  // root:[N,32]
auto cross = tp::aslinearoperator(
    tp::pairwise_expression(test, X, "rbf", true, source));
auto prior = tp::pairwise_expression(test, c10::nullopt, "rbf", true, source)
                 .diagonal();                                  // [T]
auto variance = tp::predictive_variance(cache, cross, prior);  // [T]

The variance is the latent marginal, clamped at zero; add observation noise separately for noisy predictions. One cache serves many test batches. Cache storage is O(Nr), and each call stores O(Tr) projected values. A larger rank improves the approximation at higher setup and prediction cost, and a starting vector that misses an eigenspace cannot recover it, so compare ranks on representative predictions. tp.linalg.lanczos(A, k, initial=v) exposes the Lanczos basis and tridiagonal coefficients that LOVE uses. Setup requires no-grad for gradient-bearing inputs; gradients flow through the cross kernel and prior, not through the training solve or cache.

Landmarks and low-rank factors can also be selected without storing a Gram matrix. farthest_point_sampling takes a scalar self-distance map and returns indices with their insertion distances. kmeans_plusplus needs squared distances and returns random seeds. Both read one row per step, evaluating O(Nk) entries. tp.linalg.pivoted_cholesky(K, rank) instead builds a low-rank factor with its pivots from the diagonal and one column per pivot.

Python
# X:[N,D]; inference-only selectors
with torch.no_grad():
    D = tp.lazy(X, metric="euclidean")
    indices, insertion = tp.neighbors.farthest_point_sampling(D, 16)  # [16], [16]
    D2 = tp.lazy(X, metric="sqeuclidean")
    generator = torch.Generator(device=X.device).manual_seed(7)
    seeds = tp.neighbors.kmeans_plusplus(D2, 16, generator=generator)  # [16]
    pivoted = tp.linalg.pivoted_cholesky(
        tp.lazy(X, metric="rbf", gamma=.2), rank=16, rtol=1e-6)
    factor = pivoted.factor  # [N,16], padded with zero columns
C++ / LibTorch
namespace tp = torchpairwise::ops;
// X:[N,D]
at::NoGradGuard no_grad;
auto D = tp::aslinearoperator(
    tp::pairwise_expression(X, c10::nullopt, "euclidean"));
auto [indices, insertion] = tp::farthest_point_sampling(D, 16);  // [16], [16]
auto D2 = tp::aslinearoperator(
    tp::pairwise_expression(X, c10::nullopt, "sqeuclidean"));
auto seeds = tp::kmeans_plusplus(D2, 16);  // [16]
tp::PairwiseOperationOptions source;
source.comparison.gamma = .2;
auto K = tp::aslinearoperator(
    tp::pairwise_expression(X, c10::nullopt, "rbf", true, source));
auto pivoted = tp::pivoted_cholesky(K, 16, 1e-6);  // pivoted.factor:[N,16]

The selected indices can supply the landmarks of a Nyström preconditioner, as in low-rank preconditioning. See the GP prediction notes for the LOVE construction and selector contracts.

Memory and Reuse

With b=block_size, k=min(N,max_iter), SLQ/actions retain O(Nbk) double basis entries and O(bk²) projected workspace per batch, plus RHS/output, probes/samples and source preparation/tiles. Action output uses the source dtype. Explicit probe tensors remain caller-owned; generated probes are created in blocks. Taking b or k near N can make the basis itself large. On small problems, Lanczos basis/setup workspace can exceed dense Cholesky storage, and repeated products can take longer than direct factorization. Trace uses O(Nb) action workspace; plain source diagonals may use less. Exact logdet intentionally uses a full double matrix and factorization workspace.

Native solvers prepare source metadata once per call. Plain sources bypass scalar-expression interpretation, and a stored matrix uses a direct ATen product. General scalar expressions still evaluate tiles. Public operators reference inputs so value mutations remain visible; reconstruct after resizing. There is no persistent cross-call numerical cache or retained Krylov history.

Streamed operators

Reductions and Statistics

For scalar source \(A[\ldots,N,M]\), dim=-1 selects references M, dim=-2 queries N, and dim=None selects both sample axes. Batches persist. Basic reducers and moments accept dimension lists/tuples for one joint reduction; keepdim=True retains reduced axes. Python uses .sum(...); C++ uses .reduce("sum",...) on ScalarExpression.

OperationResult / convention
sum, mean, prodNumeric values; operation-specific empty-axis identities
min, maxWith an integer dimension, values and indices; first eligible reference on ties
amin, amaxValues only, propagating NaNs
argmin, argmaxIndices only
all, anyBoolean values under PyTorch nonzero truth
var, stdcorrection=1 by default
weighted_mean, weighted_var, weighted_stdNonnegative confidence weights, normalized over the selected axis; correction=0 by default
logsumexp, count_nonzeroStable log-sum-exp / integer nonzero count
topkOne integer axis; largest=True by default; values and indices
median, quantileOne integer sample axis, default -1; values only; scalar q in [0,1]
Python
A = tp.lazy(X, Y, metric="sqeuclidean")         # [N,M]
mean = A.mean(dim=-1)                           # [N]
spread = A.var(dim=(-2, -1), correction=0)      # scalar per batch
cutoff = A.quantile(0.9, dim=-1)                # [N]
weighted = A.weighted_mean(confidence, dim=-1)  # confidence:[M], result:[N]
C++ / LibTorch
auto A = tp::pairwise_expression(X, Y, "sqeuclidean");
auto mean = A.reduce("mean", -1);  // [N]
auto spread = A.statistics("var", at::IntArrayRef{-2, -1}, false, false, 0.);
auto cutoff = A.quantile(0.9, -1);                // [N]
auto weighted = A.weighted_mean(confidence, -1);  // confidence:[M] -> [N]

Selection Details

Top-k accepts one integer axis, as PyTorch does. Stable ties use smaller original indices; NaNs have high ordering, so largest=True puts them first. Operators include self unless exclude_self=True; exclusion removes positional i=j entries from a square comparison, including separate X/Y inputs.

Unlike PyTorch, median(dim) returns values only. Median selects the lower middle value. Quantile supports linear, lower, higher, nearest and midpoint interpolation, with a deterministic witness policy. Exact selection stores compact witnesses but sorts complete reduction-axis rows in budgeted blocks; workspace scales with row length. It is not a constant-memory approximate quantile sketch.

Pair Masks

A pair mask selects the eligible \((i,j)\) entries of the logical [...,N,M] source without materializing it. Pass it as mask= to a scalar consumer, or bind it with A.masked(mask, value=0) to get a lazy expression for products, solvers and further algebra. A masked consumer equals the dense consumer applied to torch.where(mask, A, fill): the fill removes an entry from that reduction (0 for sums, +inf for minima, -inf for maxima and logsumexp). Means, variances and weighted statistics instead divide by the eligible count. median, quantile and predicate all/any reject masks. A row without any eligible entry returns the reduction identity, such as NaN for a mean, instead of raising.

The tp.masks constructors are same_group(labels, other=None), different_group(labels, other=None), causal(offset=0), band(lower=0, upper=0) and diagonal(). Combine masks with &, | and ~. Any Boolean lazy expression with the same axes, such as D < radius, and any dense Boolean [...,N,M] tensor are masks too.

Python
# X:[N,D], labels:[N] int64, V:[N,C]
D = tp.lazy(X, metric="sqeuclidean")
positives = tp.masks.same_group(labels) & ~tp.masks.diagonal()
negatives = tp.masks.different_group(labels)
hardest_positive = D.amax(-1, mask=positives)               # [N]
hardest_negative, nearest = D.min(-1, mask=negatives)       # values, indices [N]
mean_positive = D.mean(-1, mask=positives)                  # divided by eligible count
attention = (-D).softmax_matmul(V, mask=tp.masks.causal())  # [N,C]
C++ / LibTorch
#include <limits>
namespace tp = torchpairwise::ops;
// X:[N,D], labels:[N] int64, V:[N,C]. Bind masks, then fill explicitly.
constexpr double inf = std::numeric_limits<double>::infinity();
auto D = tp::pairwise_expression(X, c10::nullopt, "sqeuclidean");
auto positives = tp::same_group_mask(D, labels)
                     .logical_and(tp::diagonal_mask(D).logical_not());
auto negatives = tp::different_group_mask(D, labels);
auto hardest_positive = D.masked(positives, -inf).reduce("max", -1);  // [N]
auto hardest_negative = D.masked(negatives, inf).reduce("min", -1);   // [N]
auto mean_positive = D.masked(positives).reduce("sum", -1) /
                     positives.reduce("sum", -1);  // [N]
auto attention = D.scale(-1.).masked(tp::causal_mask(D), -inf)
                     .softmax_matmul(V);  // [N,C]

Masks are fills, not structural exclusion: when a row has fewer than k eligible entries, topk returns masked positions holding fill values, so check values.isfinite(). exclude_self still removes the diagonal structurally and composes with a mask. Gradients follow torch.where; label tensors receive none. Masked consumers use tiled execution rather than the fused CUDA reductions. See the pair mask notes for the complete consumer table, gradient rules and KeOps lowering.

Event Axes versus Sample Axes

Event-valued sources expose more axes. Reducing only event axes keeps a lazy source; selecting N or M returns a Tensor. Operation order is preserved, and statistics are not generally associative: a mean of local variances is different from one joint variance. See feature and image axes.

For empty exclusions, nonfinite values, precision and all interpolation details, see the statistical contracts.

Streamed operators

Feature and Image Axes

Scalar comparisons return one value per pair. Residual and image-map sources retain multiple values for each pair, motivating feature/spatial tiling.

SourceLogical shapeEvent axes
Feature residual[...,N,M,D]D
Image map[...,N,M,C,Hout,Wout]C, Hout, Wout

Residuals use X−Y, with identity, abs or square transforms. Image sources expose aligned Y−X, absolute error, squared error, SSIM or dissimilarity maps according to their selected comparison. They do not align, resize or register images.

Event-Only Reductions Stay Lazy

Python
# X:[N,D], Y:[M,D]
G = tp.residual_operator(X, Y, transform="abs",
                         chunk_size=64, feature_chunk_size=32)  # [N,M,D]
A = G.mean(dim=-1)                          # lazy [N,M]
values, indices = A.topk(3, largest=False)  # [N,3]
joint = G.mean(dim=(-2, -1))                # Tensor [N], joint over M,D
C++ / LibTorch
// X:[N,D], Y:[M,D]
tp::ResidualOperator G(X, Y, "abs", 64, 32);    // [N,M,D]
auto A = G.mean(-1);                            // lazy [N,M]
auto [values, indices] = A.topk(3, -1, false);  // [N,3]
auto joint = G.mean(at::IntArrayRef{-2, -1});   // Tensor [N]

The basic reducers sum/mean/var/std/min/max/prod/all/any apply to current logical axes. Selecting only events keeps the source lazy; once N or M is selected, the result is an ordinary Tensor and later operations do not fuse back into it.

An image chain can reduce Hout/Wout first, leaving channels, then reduce C, then select over M. maps.mean(dim=(-2,-1)).mean(dim=-1).amax(dim=-1) therefore means average spatial values, average channels, then the largest reference score. It is not interchangeable with a joint maximum over all axes.

Residual feature top-k and image-event top-k return terminal selected values and indices along one integer axis. Their outputs are Tensors, not new lazy sources. Scalar median/quantile require all event axes to be contracted first.

Regional Execution

Large image reductions can evaluate only the spatial regions needed for their output and replay regions during backward. feature_chunk_size similarly slices residual D. This bounds generated pair-event workspaces; source images, collection features, output tensors and gradients still exist. Some event chains retain whole earlier-stage maps; chunk controls do not guarantee a fixed total peak. See event shape rules and image-map contracts.

Example gallery

Streaming ML Algorithms

Use a comparison matrix through its action on vectors or small blocks. These examples keep the algorithm's coefficients, projections or scores, while generating kernel tiles as needed. The diagrams show computation paths, not measured performance.

Kernel ridge: apply a shifted kernel through CG to obtain an N by R coefficient block. 01 / SOLVEKernel Ridge RegressionCGKernel products

Fit regularized coefficients by solving a system through streamed products.

X [N,D], targets [N,R] → predictions [N,R]
Kernel PCA: apply a centered kernel to a narrow basis and solve an r by r eigenproblem. 02 / EMBEDTruncated Kernel PCALeading eigenpairsCentering

Find a low-dimensional embedding with a narrow subspace and a small eigensolve.

X [N,D] → coordinates [N,k]
Kernel LDA: group projection and scatter products lead to a C by C class-space eigenproblem. 03 / SEPARATEKernel LDAGroup projectionClass-space solve

Apply within-class scatter as a map, then diagonalize in the smaller class space.

X [N,D], labels [N] → coordinates [N,k]
Binary SVM: a label-weighted kernel feeds a box-and-equality QP, yielding N dual coefficients. 04 / OPTIMIZEBinary Kernel SVMConstrained QPKKT intercept

Train a binary C-SVM with box bounds and the equality required for a free intercept.

X [N,D], labels [N] → scores [N]
Landmark regression: a rectangular training kernel feeds LSQR, then a held-out kernel applies the landmark coefficients. 05 / LEAST SQUARESLandmark Kernel RegressionRectangular LSQRHeld-out prediction

Fit a kernel dictionary without storing its design matrix or normal matrix. Apply the coefficients to new samples.

X [N,D], landmarks [L,D] → coefficients [L,R]
X_test [T,D] → predictions [T,R]
PageRank: row-normalized directed affinity tiles feed a nonsymmetric GMRES solve, producing personalized vertex ranks. 06 / RANKPersonalized PageRankRestarted GMRESDirected graph

Rank vertices of a generated directed affinity graph through a nonsymmetric linear system.

Query/key views [N,D], personalization [N,R]
→ ranks [N,R]
Kernel fitting: a streamed kernel and small constraint blocks form an indefinite KKT map, solved by short-recurrence MINRES for coefficients and an intercept. 07 / CONSTRAINKernel Fitting with an InterceptSymmetric indefinite MINRESBlock KKT map

Fit kernel coefficients and an unpenalized intercept through a compact block system, then predict held-out samples.

X [N,D], targets [N,R]
→ coefficients [N,R], intercept [1,R]
Rectangular SVD: streamed forward and transpose products fit narrow left and right factors, which reuse new matrix products without evaluating pairs. 08 / APPROXIMATERectangular Truncated SVDTwo-sided iterationReusable factors

Fit a low-rank rectangular kernel map, then apply its approximation through narrow factors.

A [N,M] → U [N,k], singular values [k], V [M,k]

Use tp.linalg.solve(..., method=...) for square systems and tp.linalg.lstsq(...) for least squares; the named solver APIs remain available. These entries preserve native convergence diagnostics and do not form an inverse or pseudoinverse. See choosing a solver.

Storage and Computation

Here N is the training sample count, D the input feature count, R the number of target columns, C the number of classes, k the requested embedding dimension, and r the PCA search-subspace width. Landmark regression uses L landmarks and T held-out samples.

ExampleDense formulationMain retained algorithm blocks
Ridge[N,N] shifted Gram matrix and a direct solveCoefficients, RHS and CG vectors [N,R]
PCA[N,N] centered Gram matrix and a full eigendecompositionBasis/products [N,r], projection [r,r], coordinates [N,k]
LDA[N,N] Gram and scatter matricesClass blocks [N,C], class eigenproblem [C,C], coordinates [N,k]
SVM[N,N] label-weighted Gram matrixDual coefficients, gradients and directions [N]
Landmark regression[N,L] kernel design and an augmented least-square factorizationLSQR blocks [N,R] and [L,R], predictions [T,R]
PageRank[N,N] transition matrix and a nonsymmetric solveRow mass [N], ranks [N,R], restart basis [N,R,m] and QR blocks [R,m,m]
Ridge with intercept[N+1,N+1] symmetric indefinite KKT matrixMINRES blocks [N+1,R], constraint [1,N], intercept [1,R]

These are block shapes, not total peak-memory estimates. Inputs, kernel preparation, tile workspace, diagnostics and temporary solver blocks also use memory. A streamed product still evaluates all pairs, and iterative methods may repeat that work many times. Streaming trades stored matrices for recomputation; it does not guarantee a faster solve. PCA with r near N, or LDA with C near N, loses much of the storage advantage. For GMRES, m is the effective restart width; a width near N can also approach quadratic basis storage. Its basis and QR workspace use float64.

These examples use real float32/float64 inputs. Most run without autograd; learning examples explicitly enable first gradients. Check convergence diagnostics before using results. Native solvers are available in Python and C++; the runnable teaching scripts are Python. They illustrate algorithms rather than fitted-estimator APIs.

Measure Complete Fits

Use the algorithm benchmark harness for end-to-end ridge, landmark, PCA, LDA and SVD comparisons. It reports time, extra peak allocated CUDA bytes, numerical errors and convergence diagnostics. The default dense baseline uses the same iterative solver and settings as streaming; --dense-direct adds a separately labeled direct dense solve or full decomposition. Kernel construction and held-out prediction are included; SVD measures factor fitting. Ridge/landmark also accept --backward.

python examples/benchmark_ml.py --case pca --device cuda --n 2048 --d 8 \
    --rank 2 --subspace-size 10 --chunk-size 128 --output develop/pca-memory.json

The measured comparison table reports complete fits and backward workloads, plus a separate direct-dense ridge comparison.

CPU tensor peaks are unmeasured. GPU figures exclude reserved allocator cache and driver/external-library allocations. Streaming may take more time to save memory; see measurement scope.

Kernel Ridge Regression

For a symmetric PSD kernel K and positive regularization \(\alpha\), solve for coefficients W, then apply the kernel to predict:

\[ (K+\alpha I)W=Y,\qquad \widehat Y=KW \]

The example uses no fitted intercept. The dense equivalent is torch.linalg.solve(K + alpha * I, Y), followed by K @ W.

Python
import torch
import torchpairwise as tp

# X:[N,D], targets:[N,R], matching float32/float64 dtype and device
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.3, chunk_size=64)
    A = K + 0.2 * tp.eye_like(K)
    fit = tp.linalg.solve(A, targets, method="cg", rtol=1e-6, max_iter=300)
    if not fit.converged.all():
        raise RuntimeError(f"CG status: {fit.status.tolist()}")
    predictions = K.matmul(fit.solution)  # [N,R]
C++ / LibTorch
#include <torchpairwise.h>
namespace s = torchpairwise::streaming;

// X:[N,D], targets:[N,R], matching float32/float64 dtype and device
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.3;
options.chunk_size = 64;
auto K = s::kernel(X, c10::nullopt, "rbf", options);
auto A = s::aslinearoperator(K).add_diagonal(0.2);
s::linalg::SolveOptions solve;
solve.method = "cg";
solve.rtol = 1e-6;
solve.max_iter = 300;
auto fit = s::linalg::solve(A, targets, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "CG did not converge");
auto predictions = K.matmul(fit.solution);  // [N,R]

Full ridge example prints coefficient shape, iterations, residuals and training RMSE. Run from the repository root:

python examples/kernel_ridge.py --device cuda --dense-check

For unevenly scaled systems, the preconditioned CG example compares iterations, solve time and incremental CUDA memory with Jacobi-PCG. It uses the same streamed matrix in both paths and a separate optional dense oracle. Jacobi does not improve a constant-diagonal RBF Gram matrix by itself. The Nyström-preconditioned ridge example stores landmark kernel blocks to prepare a reusable inverse action, then solves the full original kernel system. It compares CG, Jacobi and Nyström, reports setup separately from solves, and checks coefficients and held-out predictions against a small dense oracle. See low-rank preconditioning.

Learning Through a Solve

The differentiable ridge example learns an RBF feature scale from a held-out regression loss. It uses preconditioned solve(..., differentiable=True) followed by a rectangular prediction product; the optional dense check compares both loss and scale gradient. Backward solves an adjoint system and replays streamed products, retaining no iteration history. This mode requires nonsingular systems and convergence of both solves. Python and C++ examples show its options and restrictions. --landmarks 8 switches to a damped rectangular fit using lstsq(..., differentiable=True); the same script checks the resulting loss and scale gradient against augmented dense QR.

python examples/differentiable_kernel_ridge.py --device cuda --dense-check
python examples/differentiable_kernel_ridge.py --device cuda --landmarks 8 --dense-check

Truncated Kernel PCA

Center the Gram operator, estimate its leading k eigenpairs using a search subspace of width r, and form unwhitened training coordinates:

\[ H=I-\mathbf1\mathbf1^\mathsf T/N,\qquad K_c=HKH,\qquad K_cU_k\approx U_k\Lambda_k,\qquad Z=U_k\Lambda_k^{1/2} \]

The dense equivalent centers K explicitly and calls torch.linalg.eigh(Kc), retaining the largest k eigenpairs. Gram eigenvalues divided by N-1 give the corresponding sample-covariance eigenvalues. The example also projects held-out samples using the training kernel means and fitted directions.

Python
# Reuse the imports above. X:[N,D], N>=12
with torch.no_grad():
    A = tp.lazy(X, metric="rbf", gamma=0.2).centered()
    fit = tp.linalg.eigh(A, k=4, subspace_size=12, max_iter=100,
                       rtol=1e-6, atol=1e-10)
    if not fit.converged.all():
        raise RuntimeError(f"Eigen status: {fit.status.tolist()}")
    coordinates = fit.eigenvectors * fit.eigenvalues.clamp_min(0).sqrt()
    # coordinates:[N,4]
C++ / LibTorch
// Reuse the include/namespace above. X:[N,D], N>=12
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.2;
auto A = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", options)).centered();
s::linalg::EighOptions solve;
solve.k = 4;
solve.subspace_size = 12;
solve.max_iter = 100;
solve.rtol = 1e-6;
solve.atol = 1e-10;
auto fit = s::linalg::eigh(A, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "Eigensolve did not converge");
// [N,4]
auto coordinates = fit.eigenvectors * fit.eigenvalues.clamp_min(0).sqrt();

Full PCA example prints coordinate shape, leading eigenvalues, iterations and eigen-residuals. The dense check compares spectra/subspaces rather than eigenvector signs.

python examples/kernel_pca.py --device cuda --dense-reference

PCA Projection for New Samples

Fit the training column means once. With C [T,N] comparing held-out inputs against the training inputs, the unwhitened transform is:

\[ \mu_j=\frac1N\sum_i K_{ij},\qquad C_c=(C-\mathbf1_T\mu^\mathsf T)H_N,\qquad Z_{\rm new}=C_c U_k\Lambda_k^{-1/2}\in\mathbb R^{T\times k} \]
Python
# Inside the no-grad scope above; X_new:[T,D]. Reuse the fitted eigensolve.
K = tp.lazy(X, metric="rbf", gamma=0.2)
reference_mean = K.reduce("mean", dim=-2)  # [N], fit once
cutoff = torch.finfo(X.dtype).eps * X.shape[0] * fit.eigenvalues.abs().max()
positive = fit.eigenvalues > cutoff
safe = torch.where(positive, fit.eigenvalues, torch.ones_like(fit.eigenvalues))
inverse_root = torch.where(positive, safe.rsqrt(), torch.zeros_like(safe))
weights = fit.eigenvectors * inverse_root  # [N,k]
C = tp.lazy(X_new, X, metric="rbf", gamma=0.2)
held_out = C.centered(reference_mean).matmul(weights)  # [T,k]
C++ / LibTorch
// Inside the no-grad scope above; X_new:[T,D], same training references/options.
auto K = s::kernel(X, c10::nullopt, "rbf", options);
auto reference_mean = K.reduce("mean", -2);  // [N], fit once
auto cutoff = std::numeric_limits<double>::epsilon() * X.size(0) *
              fit.eigenvalues.abs().max();  // X is float64 in this example
auto positive = fit.eigenvalues > cutoff;
auto safe = at::where(positive, fit.eigenvalues, at::ones_like(fit.eigenvalues));
auto inverse_root = at::where(positive, safe.rsqrt(), at::zeros_like(safe));
auto weights = fit.eigenvectors * inverse_root;  // [N,k]
auto C = s::aslinearoperator(s::kernel(X_new, X, "rbf", options));
auto held_out = C.centered(reference_mean).matmul(weights);  // [T,k]

Include <limits> for the C++ cutoff. Null/tiny eigenvalues receive zero projection weight. Keep training samples, kernel settings, means and weights for reuse. Coordinates are independent of which other queries accompany a sample. Only kernel tiles and projection blocks are evaluated; fitting still needs the training samples and narrow solver state.

Kernel LDA

Let E [N,C] be the class-indicator matrix, \(n_c\) the class counts, and \(P=E\operatorname{diag}(n_c^{-1})E^\mathsf T\) the group-mean projector. The example uses coefficient-space regularization \(\alpha I\):

\[ S_w=K(I-P)K+\alpha I,\qquad F=KH E\operatorname{diag}(n_c^{-1/2}),\qquad S_b=FF^\mathsf T \]

Solve \(S_w Z=F\) with CG and diagonalize \(F^\mathsf T Z\), which is [C,C]. For its positive eigenpairs \((\theta,u)\), a discriminant direction is \(Zu/\sqrt\theta\). There are at most C-1 nonzero directions. The dense equivalent forms the [N,N] scatter matrices and solves the same generalized eigenproblem. This regularizer differs from adding \(\alpha I\) in implicit feature space.

Python
# X:[N,D], labels:[N] int64. Core scatter map; full algorithm linked below.
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.3)
    P = tp.group_projection(labels, like=X)
    R = K - P @ K  # (I-P)K
    Sw = R.mT @ R
    Sw = Sw + 0.2 * tp.eye_like(Sw)  # logical [N,N]
    # For class block F:[N,C], solve Sw Z = F via tp.linalg.cg(Sw, F).
C++ / LibTorch
// X:[N,D], labels:[N] int64. Core scatter map.
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.3;
auto K = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", options));
auto P = s::group_projection(labels, X);
auto R = K - P.compose(K);
auto Sw = R.transpose().compose(R).add_diagonal(0.2);
// For F:[N,C], solve Sw Z = F via s::linalg::cg(Sw, F, solve_options).

Full LDA example constructs F, solves the class block, extracts directions and prints coordinate shapes, eigenvalues and CG diagnostics. The dense check verifies the generalized eigenproblem and scatter orthogonality.

python examples/kernel_lda.py --device cuda --dense-check

Binary Kernel SVM

For labels \(y_i\in\{-1,+1\}\) and penalty C, the dual problem is:

\[ \min_\alpha\ \tfrac12\alpha^\mathsf TQ\alpha-\mathbf1^\mathsf T\alpha, \qquad Q=\operatorname{diag}(y)K\operatorname{diag}(y),\qquad 0\le\alpha_i\le C,\quad y^\mathsf T\alpha=0 \]

The equality permits a free intercept. Training scores are \(K(\alpha\odot y)+b\). The example obtains b from free support vectors or, when none are free, the KKT intercept interval. Its dense reference uses the same QP with a materialized Q, comparing objective and predictions; the example also checks the primal-dual gap.

Python
# X:[N,D], labels:[N] floats {-1,+1}, both classes present
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=0.5)
    Q = K.weight_rows(labels).weight_columns(labels)
    fit = tp.optimize.box_qp(Q, -torch.ones_like(labels), lower=0., upper=1.,
                            equality=(labels, 0.), rtol=1e-6, max_iter=1500)
    if not fit.converged:
        raise RuntimeError(f"QP status: {fit.status.item()}")
    raw_scores = K.matmul(fit.solution * labels)  # [N], before intercept
C++ / LibTorch
// X:[N,D], labels:[N] floats {-1,+1}
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.5;
options.chunk_size = 128;
auto K = s::kernel(X, c10::nullopt, "rbf", options);
auto Q = s::aslinearoperator(K.weight_rows(labels).weight_columns(labels));
s::optimize::BoxQPOptions solve;
solve.lower = at::scalar_tensor(0., X.options());
solve.upper = at::scalar_tensor(1., X.options());
solve.equality = s::optimize::QPEquality{labels, at::scalar_tensor(0., X.options())};
solve.rtol = 1e-6;
solve.max_iter = 1500;
auto fit = s::optimize::box_qp(Q, -at::ones_like(labels), solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "QP did not converge");
auto raw_scores = K.matmul(fit.solution * labels);  // [N], before intercept

Full SVM example includes the intercept, training scores, constraint residuals and primal-dual gap. A second run with a small penalty exercises the case with no free support vectors on this seeded dataset.

python examples/kernel_svm.py --device cuda --dense-reference
python examples/kernel_svm.py --device cuda --C 0.01 --dense-reference

Landmark Kernel Regression

A kernel dictionary gives each input L explicit features, one per landmark. Unlike the square Gram systems above, its design matrix A is rectangular:

\[ \begin{aligned} A&=K(X,Z)\in\mathbb R^{N\times L}\\ W_*&\in\arg\min_W\ \|AW-Y\|_F^2+\delta^2\|W\|_F^2\\ \widehat Y_{\mathrm{test}}&=K(X_{\mathrm{test}},Z)W_* \end{aligned} \]

LSQR uses A and its transpose without forming A.T A. A nonzero data residual is expected when the targets are not exactly representable by the dictionary. Convergence checks the true normal residual \(A^\mathsf T(Y-AW)-\delta^2 W\). This example uses Euclidean coefficient regularization and no fitted intercept; it is distinct from exact kernel ridge and Nyström-whitened features.

Python
# X:[N,D], landmarks:[L,D], targets:[N,R], X_test:[T,D]
with torch.no_grad():
    A = tp.lazy(X, landmarks, metric="rbf", gamma=0.3, chunk_size=64)
    fit = tp.linalg.lstsq(A, targets, damp=0.2, rtol=1e-6, max_iter=300)
    if not fit.converged.all():
        raise RuntimeError(f"LSQR status: {fit.status.tolist()}")
    predictions = tp.lazy(X_test, landmarks, metric="rbf", gamma=0.3).matmul(fit.solution)
    # fit.solution:[L,R], predictions:[T,R]
C++ / LibTorch
// X:[N,D], landmarks:[L,D], targets:[N,R], X_test:[T,D]
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.3;
options.chunk_size = 64;
auto A = s::aslinearoperator(s::kernel(X, landmarks, "rbf", options));
s::linalg::LstsqOptions solve;
solve.damp = 0.2;
solve.rtol = 1e-6;
solve.max_iter = 300;
auto fit = s::linalg::lstsq(A, targets, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "LSQR did not converge");
auto predictions = s::kernel(X_test, landmarks, "rbf", options).matmul(fit.solution);
// fit.solution:[L,R], predictions:[T,R]

Full landmark regression example prints held-out RMSE, coefficient/output shapes and data/normal residuals. The dense reference solves [A; damp I] W ≈ [Y; 0] with torch.linalg.lstsq, then compares coefficients, objective and held-out predictions. LSQR also accepts wide, rank-deficient and nonsymmetric maps. See the complete least-squares contract for minimum-norm, warm-start and stopping conventions.

python examples/landmark_regression.py --device cuda --dense-reference

Personalized PageRank

Give each vertex a query and a key feature vector. Their RBF affinity need not be symmetric. Add a small uniform weight to guarantee positive outgoing mass, then normalize each row:

\[ \begin{aligned} W_{ij}&=K(q_i,k_j)+\varepsilon/N\\ P_{ij}&=W_{ij}/\sum_jW_{ij}\\ (I-\beta P^\mathsf T)R&=(1-\beta)V \end{aligned} \]

Each column of V is a nonnegative personalization vector summing to one. With \(0\le\beta<1\), the resulting ranks give the stationary distribution of the teleporting walk. Restarted GMRES uses actions of \(I-\beta P^\mathsf T\), without constructing the identity or the transition matrix. This is a generated dense graph with self edges, not a sparse web-graph implementation. Row normalization of a symmetric affinity would instead give a reversible walk; the separate query/key features make this example directed.

Python
# X, keys:[N,D], two views of the same N vertices; N>0
with torch.no_grad():
    W = tp.lazy(X, keys, metric="rbf", gamma=0.3) + X.new_tensor(0.01 / X.shape[0])
    P = W.weight_rows(W.sum(dim=-1).reciprocal())
    A = tp.eye_like(P) - 0.85 * P.mT
    rhs = X.new_full((X.shape[0], 1), 0.15 / X.shape[0])
    fit = tp.linalg.gmres(A, rhs, restart=12, rtol=1e-8, max_iter=200)
    if not fit.converged.all():
        raise RuntimeError(f"GMRES status: {fit.status.tolist()}")
    ranks = fit.solution  # [N,1]
C++ / LibTorch
// X, keys:[N,D], N>0
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.3;
auto W = s::kernel(X, keys, "rbf", options) +
    s::ScalarExpression(at::scalar_tensor(0.01/X.size(0), X.options()));
auto P = W.weight_rows(W.reduce("sum", -1).reciprocal());
auto A = s::aslinearoperator(P).transpose().scale(-0.85).add_diagonal(1.);
auto rhs = at::full({X.size(0), 1}, 0.15/X.size(0), X.options());
s::linalg::GMRESOptions solve;
solve.restart = 12;
solve.rtol = 1e-8;
solve.max_iter = 200;
auto fit = s::linalg::gmres(A, rhs, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "GMRES did not converge");
auto ranks = fit.solution;  // [N,1]

The dense equivalent is torch.linalg.solve(I-beta*P.T, (1-beta)*V). The full PageRank example uses two personalization columns and prints iteration counts, residuals and rank-vector mass. The dense check also verifies stationarity, nonnegativity and mass; it does not clip or renormalize solver output. See GMRES contracts for restart storage, stopping and breakdown conventions.

python examples/pagerank.py --device cuda --restart 12 --dense-reference

Kernel Fitting with an Intercept

The first ridge example fixes the intercept at zero. To learn an unpenalized intercept b [1,R], solve for coefficients W [N,R] under \(\mathbf1^\mathsf T W=0\):

\[ \begin{bmatrix}K+\alpha I&\mathbf1\\\mathbf1^\mathsf T&0\end{bmatrix} \begin{bmatrix}W\\b\end{bmatrix} =\begin{bmatrix}Y\\0\end{bmatrix} \]

For a symmetric PSD kernel and positive alpha, the KKT map is symmetric indefinite. tp.block combines the streamed kernel with a stored ones vector and a [1,1] zero corner. MINRES acts on this map without storing the full system. Held-out predictions are \(K(X_{\mathrm{test}},X)W+\mathbf1 b\).

Python
# X:[N,D], targets:[N,R], N>0
with torch.no_grad():
    A = tp.lazy(X, metric="rbf", gamma=0.3)
    A = A + 0.3 * tp.eye_like(A)
    C = tp.as_lazy(X.new_ones(1, X.shape[0]))
    KKT = tp.block([[A, C.T], [C, X.new_zeros(1, 1)]])
    rhs = torch.cat((targets, targets.new_zeros(1, targets.shape[-1])), -2)
    fit = tp.linalg.minres(KKT, rhs, rtol=1e-8, max_iter=300)
    if not fit.converged.all():
        raise RuntimeError(f"MINRES status: {fit.status.tolist()}")
    weights, intercept = fit.solution[:-1], fit.solution[-1:]
C++ / LibTorch
// X:[N,D], targets:[N,R], N>0
at::NoGradGuard no_grad;
s::PairwiseOptions options;
options.comparison.gamma = 0.3;
auto A = s::aslinearoperator(s::kernel(X, c10::nullopt, "rbf", options)).add_diagonal(0.3);
auto C = s::aslinearoperator(at::ones({1, X.size(0)}, X.options()));
auto D = s::aslinearoperator(at::zeros({1, 1}, X.options()));
auto KKT = s::block({{A, C.transpose()}, {C, D}});
auto rhs = at::cat({targets, at::zeros({1, targets.size(-1)}, targets.options())}, -2);
s::linalg::MINRESOptions solve;
solve.rtol = 1e-8;
solve.max_iter = 300;
auto fit = s::linalg::minres(KKT, rhs, solve);
TORCH_CHECK(fit.converged.all().item<bool>(), "MINRES did not converge");
auto weights = fit.solution.slice(-2, 0, X.size(0));
auto intercept = fit.solution.slice(-2, X.size(0), X.size(0)+1);

The full intercept example prints coefficients, intercepts, Lanczos steps, KKT residuals and held-out RMSE. The dense reference checks both a direct KKT solve and an independent Schur complement using SPD solves, plus the zero-sum coefficient constraint and held-out predictions. The Schur formulation is another valid approach for this small constraint system; the example demonstrates direct indefinite block solves. See block maps and MINRES contracts.

python examples/kernel_ridge_intercept.py --device cuda --dense-reference

Rectangular Low-Rank Approximation

The rectangular SVD example decomposes an RBF cross-kernel [N,M] through tp.linalg.svd, retaining left [N,k] and right [M,k] factors. It then applies an approximate product with new RHS [M,R] using only those factors. The dense check compares singular values, reconstruction and that product against torch.linalg.svd. Fitting still requires repeated pairwise passes; subsequent approximate products reuse the factors without pair evaluations.

\[ AV_k\approx U_k\Sigma_k,\qquad A_k B=U_k\bigl(\Sigma_k(V_k^\mathsf T B)\bigr)\in\mathbb R^{N\times R} \]
python examples/rectangular_svd.py --device cuda --dense-check

Use the Python/C++ SVD guide for result fields, shapes, numerical diagnostics and memory costs. The method estimates leading triplets; flat spectra can require more width or iterations.

More Streamed Consumers

The regression and attention examples show three smaller patterns, with dense value/first-gradient checks:

AlgorithmOperator consumerRetained result
k-NN regressionDistance topk(k, largest=False) and target averagingNeighbors [N,k], predictions [N,R]
Nadaraya-Watson regressionOne kernel product carrying targets and a normalizerWeighted targets and mass [N,R+1]
Scaled dot-product attentionsoftmax_matmul(values) on scaled dot productsAttended values [N,R]
python examples/streaming_ml.py --device cuda

Use --device cpu in any command for CPU execution. Dense-reference flags are intended for small inputs: they deliberately allocate the full comparison matrix. The examples print their own results; numeric outputs depend on device, dtype and convergence. See algebra and solver contracts for convergence and deployment limits, and benchmarking to measure time and memory for a workload.

Masks, Transport and Predictive Variance

The pair mask example applies masked consumers to supervised contrastive loss, hard negative mining, a Borůvka nearest-component step and causal kernel attention, each checked against a dense reference. Python and C++ snippets for these consumers are in pair masks. Two further patterns are documented with code rather than a script: Sinkhorn transport over any lazy cost in entropic transport, and LOVE predictive variances with landmark selection in Gaussian process prediction.

python examples/pair_masks.py --device cuda

Kernel Log-Determinants and Graph Diffusion

A kernel uncertainty or model-selection objective can use \(\log\det(K+\alpha I)\). A symmetric affinity graph defines the Laplacian \(L=\operatorname{diag}(K\mathbf1)-K\); diffusion acts on signals V directly:

\[ Y(t)=e^{-tL}V \]
Python
# X:[N,D], signals:[N,R], matching float32/64 dtype/device
with torch.no_grad():
    K = tp.lazy(X, metric="rbf", gamma=.2, chunk_size=64)
    A = K + .5 * tp.eye_like(K)
    uncertainty = tp.linalg.logdet(A, method="slq", num_probes=32,
                                  seed=19, max_iter=32, block_size=4)
    L = tp.diag_embed(K.reduce("sum", dim=-1)) - K
    diffused = tp.linalg.expm_multiply(L, signals, scale=-.1, max_iter=32)
    whitened = tp.linalg.inv_sqrt_multiply(A, signals, max_iter=32)
    Y = diffused.action  # [N,R]
C++ / LibTorch
at::NoGradGuard no_grad;
s::PairwiseOptions source;
source.comparison.gamma = .2;
source.chunk_size = 64;
auto K = s::kernel(X, c10::nullopt, "rbf", source);
auto A = s::aslinearoperator(K).add_diagonal(.5);
s::linalg::LogdetOptions estimate;
estimate.method = "slq";
estimate.num_probes = 32;
estimate.seed = 19;
estimate.max_iter = 32;
estimate.block_size = 4;
auto uncertainty = s::linalg::logdet(A, estimate);
auto L = s::diagonal(K.reduce("sum", -1)) - s::aslinearoperator(K);
s::linalg::MatrixFunctionOptions action;
action.max_iter = 32;
auto diffused = s::linalg::expm_multiply(L, signals, -.1, action);
auto whitened = s::linalg::inv_sqrt_multiply(A, signals, action);

The spectral example prints runtime, extra peak CUDA tensor memory, log-determinant sampling error and per-column action diagnostics. --dense-reference separately measures small dense Cholesky/logdet, matrix-exponential and eigendecomposition references, including kernel construction. It checks constant-signal preservation under diffusion. Finite Krylov/probe counts approximate results; increase the budgets and inspect dense errors where feasible.

python examples/spectral_actions.py --device cuda --samples 128 --dense-reference

Only Krylov bases, scalar samples, signal blocks and source tiles are retained; the generated N-by-N kernel, Laplacian and matrix functions are not stored. Large Krylov or block sizes can still require substantial basis memory. See spectral methods and diagnostics.

Learning Kernel Bandwidth and Noise

The likelihood example learns two scalar parameters through a native implicit CG solve and a log-determinant estimate. For A = K + noise I and one target column y,

\[ \mathcal L=\tfrac12y^\mathsf TA^{-1}y+\tfrac12\log\det A+ \tfrac N2\log(2\pi) \]
Python
# X:[N,D], y:[N,1]; log_length/log_noise are trainable scalar tensors.
features = X / log_length.exp()
K = tp.lazy(features, metric="rbf", gamma=.5)
A = K + log_noise.exp() * tp.eye_like(K)
fit = tp.linalg.solve(A, y, method="cg", differentiable=True,
                     rtol=1e-8, max_iter=500)
determinant = tp.linalg.logdet(A, method="slq", num_probes=32,
    seed=17, max_iter=24, differentiable=True, gradient="solve",
    solve_rtol=1e-8, solve_max_iter=500)
loss = .5 * (y * fit.solution).sum() + .5 * determinant.estimate
loss.backward()
C++ / LibTorch
auto features = X / log_length.exp();
s::PairwiseOptions source;
source.comparison.gamma = .5;
auto A = s::aslinearoperator(s::kernel(features, c10::nullopt, "rbf", source))
             .add_diagonal(log_noise.exp());
s::linalg::SolveOptions solve;
solve.method = "cg";
solve.differentiable = true;
solve.rtol = 1e-8;
solve.max_iter = 500;
auto fit = s::linalg::solve(A, y, solve);
s::linalg::LogdetOptions options;
options.method = "slq";
options.num_probes = 32;
options.seed = 17;
options.max_iter = 24;
options.differentiable = true;
options.gradient = "solve";
options.solve_rtol = 1e-8;
options.solve_max_iter = 500;
auto determinant = s::linalg::logdet(A, options);
auto loss = .5 * (y * fit.solution).sum() + .5 * determinant.estimate;
loss.backward();

SLQ values approximate the objective, while gradient="solve" estimates the underlying log-determinant derivative using fixed probes and converged CG solutions. It does not differentiate finite quadrature steps. Sampling error omits quadrature bias; inspect both budgets separately. The executable example reuses explicit probes across optimizer steps and optionally checks gradients against the same dense trace/solve estimator. --exact-logdet instead selects dense Cholesky and checks the ordinary exact log-determinant gradient.

python examples/kernel_likelihood.py --device cuda --dense-reference

For a linear kernel, the existing map composition also exposes an explicit factorized action, useful when the feature width is small:

\[ (XY^\mathsf T)V=X(Y^\mathsf TV) \]
Python
# X:[N,D], Y:[M,D], V:[M,R]
A = tp.as_lazy(X) @ tp.as_lazy(Y).mT
result = A.matmul(V)  # [N,R]; intermediate [D,R]
C++ / LibTorch
auto A = s::aslinearoperator(X).compose(s::aslinearoperator(Y).transpose());
auto result = A.matmul(V);  // [N,R]; intermediate [D,R]

This explicitly selects evaluation order; ordinary floating-point reassociation, overflow and nonfinite propagation can differ from a stored or tiled Gram product.

Metric reference

Comparison Catalog

87 entries across 9 input families, including 13 kernels. Entries include mathematical synonyms; each page states its equation and conventions. Use the language toggle for Python or C++ examples.

87 comparisons

Aitchison

Vectors · distance

all-pairspairedcondensedsingle
Angular

Vectors · distance

all-pairspairedcondensedsingle
Bray-Curtis

Vectors · distance

all-pairspairedcondensedsingle
Canberra

Vectors · distance

all-pairspairedcondensedsingle
Chamfer

Point sets · distance

all-pairspairedcondensedsingle
Chebyshev

Vectors · distance

all-pairspairedcondensedsingle
Clark

Vectors · distance

all-pairspairedcondensedsingle
Dice

Vectors · distance

all-pairspairedcondensedsingle
Euclidean

Vectors · distance

all-pairspairedcondensedsingle
Fisher-Rao

Vectors · distance

all-pairspairedcondensedsingle
Hamming

Vectors · distance

all-pairspairedcondensedsingle
Hausdorff

Point sets · distance

all-pairspairedcondensedsingle
Haversine

Vectors · distance

all-pairspairedcondensedsingle
Hellinger

Vectors · distance

all-pairspairedcondensedsingle
Jaccard

Vectors · distance

all-pairspairedcondensedsingle
Kulczynski 1

Vectors · distance

all-pairspairedcondensedsingle
Kulsinski

Vectors · distance

all-pairspairedcondensedsingle
Levenshtein

Tokens · distance

all-pairspairedcondensedsingle
Log-Euclidean

Covariances · distance

all-pairspairedcondensedsingle
Lorentzian

Vectors · distance

all-pairspairedcondensedsingle
Mahalanobis

Vectors · distance

all-pairspairedcondensedsingle
Manhattan

Vectors · distance

all-pairspairedcondensedsingle
Matérn

Vectors · kernel

all-pairspairedcondensedsingle
Minkowski

Vectors · distance

all-pairspairedcondensedsingle
Russell-Rao

Vectors · distance

all-pairspairedcondensedsingle
Rényi

Vectors · distance

all-pairspairedsingle
Soergel

Vectors · distance

all-pairspairedcondensedsingle
Sokal-Sneath

Vectors · distance

all-pairspairedcondensedsingle
Yule

Vectors · distance

all-pairspairedcondensedsingle
Metric reference

Vector Geometry

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Angulardistanceall_pairs, paired, condensed, single
Bray-Curtisdistanceall_pairs, paired, condensed, single
Canberradistanceall_pairs, paired, condensed, single
Chebyshevdistanceall_pairs, paired, condensed, single
Clarkdistanceall_pairs, paired, condensed, single
Correlation Distancedistanceall_pairs, paired, condensed, single
Cosine Distancedistanceall_pairs, paired, condensed, single
Euclideandistanceall_pairs, paired, condensed, single
Haversinedistanceall_pairs, paired, condensed, single
Lorentziandistanceall_pairs, paired, condensed, single
Mahalanobisdistanceall_pairs, paired, condensed, single
Manhattandistanceall_pairs, paired, condensed, single
Minkowskidistanceall_pairs, paired, condensed, single
NaN-Aware Euclideandistanceall_pairs, paired, condensed, single
Ruzicka / Continuous Jaccarddistanceall_pairs, paired, condensed, single
Soergeldistanceall_pairs, paired, condensed, single
Squared Euclideandistanceall_pairs, paired, condensed, single
Standardized Euclideandistanceall_pairs, paired, condensed, single
Variance-Ratio SNR Distancedistanceall_pairs, paired, single
Weighted Minkowskidistanceall_pairs, paired, condensed, single
Vector geometry

Angular

angular_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\arccos\!\left[\operatorname{clip}\!\left(\frac{\langle x,y\rangle}{\|x\|_2\|y\|_2},-1,1\right)\right] \]

Returns radians in [0,π]. Inputs must have nonzero norms; coincident directions use a zero subgradient.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsangular_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: angular.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "angular_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "angular_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Bray-Curtis

braycurtis_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\frac{\sum_i|x_i-y_i|}{\sum_i|x_i+y_i|} \]

Optional w scales numerator and denominator coordinates. An all-zero denominator is undefined.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsbraycurtis_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: braycurtis.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "braycurtis_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "braycurtis_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Canberra

canberra_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sum_i\frac{|x_i-y_i|}{|x_i|+|y_i|} \]

A joint zero coordinate contributes zero. Optional w scales the coordinate terms.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairscanberra_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: canberra.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "canberra_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "canberra_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Chebyshev

chebyshev_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\max_i |x_i-y_i| \]

With w, only coordinates with positive weight participate; their weight magnitudes do not scale the maximum.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairschebyshev_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: chebyshev, linf, linf_distances.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "chebyshev_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "chebyshev_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Clark

clark_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sqrt{\sum_i\left(\frac{x_i-y_i}{x_i+y_i}\right)^2} \]

Finite nonnegative coordinates. A joint zero coordinate contributes zero.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsclark_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: clark.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "clark_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "clark_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Correlation Distance

correlation_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=1-\frac{\langle x-\bar x,y-\bar y\rangle}{\|x-\bar x\|_2\|y-\bar y\|_2} \]

Optional w uses weighted means, products and norms. Constant supported rows have undefined correlation and return NaN.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairscorrelation_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: correlation.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "correlation_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "correlation_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Cosine Distance

cosine_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=1-\frac{\langle x,y\rangle}{\|x\|_2\|y\|_2} \]

Optional w gives weighted dot products and norms. Zero-norm cross comparisons return 1; identity self comparisons explicitly zero the diagonal.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairscosine_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: cosine.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "cosine_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "cosine_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Euclidean

euclidean_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sqrt{\sum_i(x_i-y_i)^2} \]

Optional w multiplies each squared coordinate difference.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairseuclidean_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: euclidean, l2, l2_distances.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "euclidean_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "euclidean_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Haversine

haversine_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=2\arcsin\sqrt{\sin^2\!\frac{\phi_x-\phi_y}{2}+\cos\phi_x\cos\phi_y\sin^2\!\frac{\lambda_x-\lambda_y}{2}} \]

Each vector is latitude/longitude in radians, D=2. Returns an angle on the unit sphere; multiply by the sphere radius for lengths.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairshaversine_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: haversine.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "haversine_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "haversine_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Lorentzian

lorentzian_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sum_i\log(1+|x_i-y_i|) \]

Natural logarithms soften the contribution of large coordinate differences.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslorentzian_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: lorentzian.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "lorentzian_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "lorentzian_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Mahalanobis

mahalanobis_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sqrt{(x-y)^\mathsf{T}\,VI\,(x-y)} \]

VI ends in [D,D] and represents the inverse covariance. Paired/single and explicit cross collections require it; self collection comparisons may infer it.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmahalanobis_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: mahalanobis.

Accepted metric options: VI. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "mahalanobis_distances"
options = dict(VI=torch.eye(2))
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "mahalanobis_distances";
tp::ComparisonOptions options;
options.VI = at::eye(2);
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Manhattan

manhattan_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sum_i|x_i-y_i| \]

Also called L1 or cityblock. Optional feature weights w.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmanhattan_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: cityblock, cityblock_distances, l1, l1_distances, manhattan.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "manhattan_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "manhattan_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Minkowski

minkowski_distances
all-pairspairedcondensedsingledistanceVectors
\[ d_p(x,y)=\left(\sum_i|x_i-y_i|^p\right)^{1/p} \]

p defaults to 2. Positive finite p uses this formula; infinity selects the maximum and p=0 counts unequal coordinates. With weights, p must be positive; p<1 is not a metric.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsminkowski_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: lp, lp_distances, minkowski.

Accepted metric options: p, w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "minkowski_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "minkowski_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

NaN-Aware Euclidean

nan_euclidean_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sqrt{\frac{D}{|O|}\sum_{i\in O}(x_i-y_i)^2} \]

O contains jointly observed coordinates. No shared observations returns NaN; infinities are rejected. squared=True omits the root.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsnan_euclidean_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: nan_euclidean.

Accepted metric options: squared. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "nan_euclidean_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "nan_euclidean_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Ruzicka / Continuous Jaccard

ruzicka_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=1-\frac{\sum_i\min(x_i,y_i)}{\sum_i\max(x_i,y_i)} \]

Finite nonnegative coordinates, with zero for two zero vectors. Equivalent to Soergel on this domain; continuous_jaccard is an alias.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsruzicka_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: continuous_jaccard, continuous_jaccard_distances, ruzicka.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "ruzicka_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "ruzicka_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Soergel

soergel_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\frac{\sum_i|x_i-y_i|}{\sum_i\max(x_i,y_i)} \]

Finite nonnegative coordinates. Two zero vectors return zero. Ruzicka has the same formula on this domain.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssoergel_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: soergel.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "soergel_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "soergel_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Squared Euclidean

sqeuclidean_distances
all-pairspairedcondensedsingledistanceVectors
\[ d^2(x,y)=\sum_i(x_i-y_i)^2 \]

Returns squared units; useful for losses and Gaussian kernels. Optional feature weights w.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssqeuclidean_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: sqeuclidean.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "sqeuclidean_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "sqeuclidean_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Standardized Euclidean

seuclidean_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=\sqrt{\sum_i\frac{(x_i-y_i)^2}{V_i}} \]

V is a positive feature-variance tensor ending in [D]. Self collection comparisons may infer it; paired/single and explicit cross collections require V.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsseuclidean_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: seuclidean.

Accepted metric options: V. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "seuclidean_distances"
options = dict(V=torch.ones(2))
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "seuclidean_distances";
tp::ComparisonOptions options;
options.V = at::ones({2});
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Vector geometry

Variance-Ratio SNR Distance

snr_distances
all-pairspairedsingledistanceVectors
\[ d(x,y)=\frac{\operatorname{Var}(y-x)}{\operatorname{Var}(x)} \]

Directed vector discrepancy using feature-axis variances; this is not an audio SNR score in decibels.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssnr_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: snr.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "snr_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "snr_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Vector geometry

Weighted Minkowski

wminkowski_distances
all-pairspairedcondensedsingledistanceVectors
\[ d_{p,w}(x,y)=\left(\sum_i w_i|x_i-y_i|^p\right)^{1/p} \]

Provide nonnegative w ending in [D]; p must be positive. Weights multiply the powered differences, rather than being raised to p.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairswminkowski_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: wminkowski.

Accepted metric options: p, w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "wminkowski_distances"
options = dict(w=torch.tensor([1., 2.]))
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "wminkowski_distances";
tp::ComparisonOptions options;
options.w = at::tensor({1.f, 2.f});
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Metric reference

Boolean and Equality

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Dicedistanceall_pairs, paired, condensed, single
Hammingdistanceall_pairs, paired, condensed, single
Jaccarddistanceall_pairs, paired, condensed, single
Kulczynski 1distanceall_pairs, paired, condensed, single
Kulsinskidistanceall_pairs, paired, condensed, single
Rogers-Tanimotodistanceall_pairs, paired, condensed, single
Russell-Raodistanceall_pairs, paired, condensed, single
Sokal-Michenerdistanceall_pairs, paired, condensed, single
Sokal-Sneathdistanceall_pairs, paired, condensed, single
Yuledistanceall_pairs, paired, condensed, single
Boolean and equality

Dice

dice_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{b+c}{2a+b+c} \]

Boolean contingency counts; both vectors empty gives NaN. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsdice_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: dice.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "dice_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "dice_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Hamming

hamming_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(x,y)=D^{-1}\sum_i\mathbf1[x_i\ne y_i] \]

Numeric or Boolean inputs; tests equality without converting numeric values to Boolean. w gives a weighted fraction.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairshamming_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: hamming.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "hamming_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "hamming_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Jaccard

jaccard_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{b+c}{a+b+c} \]

Boolean contingency counts; empty union returns zero. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsjaccard_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: jaccard.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "jaccard_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "jaccard_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Kulczynski 1

kulczynski1_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{a}{b+c} \]

A legacy ratio rather than a conventional distance; equal nonempty supports may give infinity. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairskulczynski1_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: kulczynski1.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "kulczynski1_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "kulczynski1_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Kulsinski

kulsinski_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{b+c-a+t}{b+c+t} \]

Retains the legacy Kulsinski definition, distinct from Kulczynski 1. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairskulsinski_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: kulsinski.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "kulsinski_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "kulsinski_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Boolean and equality

Rogers-Tanimoto

rogerstanimoto_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{2(b+c)}{a+d+2(b+c)} \]

Counts agreements of both true and false coordinates. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsrogerstanimoto_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: rogerstanimoto.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "rogerstanimoto_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "rogerstanimoto_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Russell-Rao

russellrao_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{t-a}{t} \]

Counts only jointly true coordinates as agreement; nonempty all-false vectors have distance 1. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsrussellrao_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: russellrao.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "russellrao_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "russellrao_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Sokal-Michener

sokalmichener_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{2(b+c)}{a+d+2(b+c)} \]

The implemented convention equals Rogers-Tanimoto; retained as a familiar named entry. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssokalmichener_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: sokalmichener.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "sokalmichener_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "sokalmichener_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Sokal-Sneath

sokalsneath_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{2(b+c)}{a+2(b+c)} \]

Both vectors empty gives NaN. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssokalsneath_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: sokalsneath.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "sokalsneath_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "sokalsneath_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Boolean and equality

Yule

yule_distances
all-pairspairedcondensedsingledistanceVectors
\[ d=\frac{2bc}{ad+bc} \]

Boolean contingency counts; an undefined zero/zero ratio uses zero. For Boolean formulas a=c11, b=c10, c=c01, d=c00 and t=a+b+c+d. Optional w replaces counts by weight sums.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsyule_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: yule.

Accepted metric options: w. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[True, False], [True, True]])  # [2,2]
Y = torch.tensor([[True, True], [False, True], [True, False]])

metric = "yule_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({true, false, true, true}, at::kBool).reshape({2, 2});
auto Y = at::tensor({true, true, false, true, true, false}, at::kBool).reshape({3, 2});

const std::string metric = "yule_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Boolean/equality inputs do not have derivatives; feature weights may have derivatives for finite weighted formulas. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Metric reference

Probability and Histograms

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Aitchisondistanceall_pairs, paired, condensed, single
Bhattacharyya Distancedistanceall_pairs, paired, condensed, single
Fisher-Raodistanceall_pairs, paired, condensed, single
Hellingerdistanceall_pairs, paired, condensed, single
Itakura-Saitodistanceall_pairs, paired, single
Jensen-Shannondistanceall_pairs, paired, condensed, single
Kullback-Leiblerdistanceall_pairs, paired, single
Neyman Chi-Squareddistanceall_pairs, paired, single
Pearson Chi-Squareddistanceall_pairs, paired, single
Rényidistanceall_pairs, paired, single
Shared-Grid 1D Wassersteindistanceall_pairs, paired, condensed, single
Symmetric KL / Jeffreysdistanceall_pairs, paired, condensed, single
Total Variationdistanceall_pairs, paired, condensed, single
Triangular Discriminationdistanceall_pairs, paired, condensed, single
Probability and histograms

Aitchison

aitchison_distances
all-pairspairedcondensedsingledistanceVectors
\[ d_A(x,y)=\|\operatorname{clr}(x)-\operatorname{clr}(y)\|_2,\quad \operatorname{clr}(x)_i=\log x_i-D^{-1}\sum_j\log x_j \]

Strictly positive finite compositions. No pseudocount; independent row scaling preserves the result.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsaitchison_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: aitchison.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "aitchison_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "aitchison_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Bhattacharyya Distance

bhattacharyya_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(p,q)=-\log\!\left(\sum_i\sqrt{p_iq_i}\right) \]

Normalizes nonnegative positive-mass rows. Disjoint supports give infinity; not a triangle-inequality metric.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsbhattacharyya_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: bhattacharyya.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "bhattacharyya_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "bhattacharyya_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Fisher-Rao

fisher_rao_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(p,q)=2\arccos\!\left(\sum_i\sqrt{p_iq_i}\right) \]

Normalizes each nonnegative positive-mass row. Uses the factor-two convention, range [0,π].

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsfisher_rao_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: fisher_rao.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "fisher_rao_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "fisher_rao_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Hellinger

hellinger_distances
all-pairspairedcondensedsingledistanceVectors
\[ H(p,q)=\frac{\|\sqrt p-\sqrt q\|_2}{\sqrt2} \]

Nonnegative positive-mass rows are normalized independently. Range [0,1]; no pseudocount.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairshellinger_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: hellinger.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "hellinger_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "hellinger_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Itakura-Saito

itakura_saito_divergences
all-pairspairedsingledistanceVectors
\[ D(x\Vert y)=\sum_i\left[\frac{x_i}{y_i}-\log\frac{x_i}{y_i}-1\right] \]

Raw strictly positive entries, without row normalization. Scaling both operands equally preserves the divergence.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsitakura_saito_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: itakura_saito, itakura_saito_distances.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "itakura_saito_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "itakura_saito_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Jensen-Shannon

jensenshannon_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(p,q)=\sqrt{\tfrac12D_{\mathrm{KL}}(p\Vert m)+\tfrac12D_{\mathrm{KL}}(q\Vert m)},\quad m=(p+q)/2 \]

Normalizes each nonnegative positive-mass row. Rooted distance; base controls logarithm units.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsjensenshannon_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: jensenshannon.

Accepted metric options: base. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "jensenshannon_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "jensenshannon_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Probability and histograms

Kullback-Leibler

kl_divergences
all-pairspairedsingledistanceVectors
\[ D_{\mathrm{KL}}(p\Vert q)=\sum_i p_i\log\frac{p_i}{q_i} \]

Normalizes nonnegative positive-mass rows. Positive p with zero q gives infinity. base changes logarithm units.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairskl_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: kl, kl_distances.

Accepted metric options: base. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "kl_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "kl_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Neyman Chi-Squared

neyman_chi2_divergences
all-pairspairedsingledistanceVectors
\[ D(p\Vert q)=\sum_i\frac{(p_i-q_i)^2}{p_i} \]

Normalizes each nonnegative positive-mass row. Reverses the Pearson denominator.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsneyman_chi2_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: neyman_chi2, neyman_chi2_distances.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "neyman_chi2_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "neyman_chi2_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Pearson Chi-Squared

pearson_chi2_divergences
all-pairspairedsingledistanceVectors
\[ D(p\Vert q)=\sum_i\frac{(p_i-q_i)^2}{q_i} \]

Normalizes each nonnegative positive-mass row. The second operand supplies the denominator; support mismatch may give infinity.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairspearson_chi2_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: pearson_chi2, pearson_chi2_distances.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "pearson_chi2_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "pearson_chi2_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Rényi

renyi_divergences
all-pairspairedsingledistanceVectors
\[ D_\alpha(p\Vert q)=\frac{\log\sum_i p_i^\alpha q_i^{1-\alpha}}{\alpha-1} \]

Normalizes each nonnegative positive-mass row. alpha>0; alpha=1 uses the KL limit. Natural logarithms.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsrenyi_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: renyi, renyi_distances.

Accepted metric options: alpha. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "renyi_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "renyi_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Shared-Grid 1D Wasserstein

wasserstein_1d_distances
all-pairspairedcondensedsingledistanceVectors
\[ W_1(p,q)=\sum_{i=1}^{D-1}|P_i-Q_i|(z_{i+1}-z_i),\quad P_i=\sum_{j\le i}p_j \]

Inputs are normalized histogram masses, not raw support samples. bin_positions supplies one strictly increasing shared grid; default unit-spaced bins.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairswasserstein_1d_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: wasserstein_1d.

Accepted metric options: bin_positions. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "wasserstein_1d_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "wasserstein_1d_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Probability and histograms

Symmetric KL / Jeffreys

symmetric_kl_divergences
all-pairspairedcondensedsingledistanceVectors
\[ J(p,q)=D_{\mathrm{KL}}(p\Vert q)+D_{\mathrm{KL}}(q\Vert p) \]

The directional KL sum, without a factor of one half or a root. Normalizes each row.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssymmetric_kl_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: symmetric_kl, symmetric_kl_distances.

Accepted metric options: base. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "symmetric_kl_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "symmetric_kl_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Total Variation

total_variation_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(p,q)=\tfrac12\sum_i|p_i-q_i| \]

Normalizes each nonnegative positive-mass row. Range [0,1].

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairstotal_variation_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: total_variation.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "total_variation_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "total_variation_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Probability and histograms

Triangular Discrimination

triangular_discrimination_distances
all-pairspairedcondensedsingledistanceVectors
\[ d(p,q)=\sum_i\frac{(p_i-q_i)^2}{p_i+q_i} \]

Normalizes each nonnegative positive-mass row. No factor of one half; range [0,2]. Joint zeros contribute zero.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairstriangular_discrimination_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: triangular_discrimination.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "triangular_discrimination_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "triangular_discrimination_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Metric reference

Point Sets and Transport

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Chamferdistanceall_pairs, paired, condensed, single
Directed Hausdorffdistanceall_pairs, paired, single
Discrete Wassersteindistanceall_pairs, paired, condensed, single
Energy Distancedistanceall_pairs, paired, condensed, single
Hausdorffdistanceall_pairs, paired, condensed, single
Kernel MMD Distancedistanceall_pairs, paired, condensed, single
Sinkhorn Divergencedistanceall_pairs, paired, condensed, single
Sliced Wassersteindistanceall_pairs, paired, condensed, single
Squared MMD Statisticstatisticall_pairs, paired, condensed, single
Point sets and transport

Chamfer

chamfer_distances
all-pairspairedcondensedsingledistancePoint sets
\[ C(X,Y)=\frac1P\sum_{x\in X}\min_{y\in Y}\rho(x,y)+\frac1Q\sum_{y\in Y}\min_{x\in X}\rho(x,y) \]

rho is Euclidean distance by default, squared Euclidean with squared=True. Returns the sum of directional means.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairschamfer_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: chamfer.

Accepted metric options: squared, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "chamfer_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "chamfer_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Directed Hausdorff

directed_hausdorff_distances
all-pairspairedsingledistancePoint sets
\[ h(X,Y)=\max_{x\in X}\min_{y\in Y}\|x-y\|_2 \]

Generic selectors return values. The named all-pairs function also returns source/target witness indices. Directional: no condensed mode.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsdirected_hausdorff_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: directed_hausdorff.

Accepted metric options: shuffle, generator, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "directed_hausdorff_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "directed_hausdorff_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Discrete Wasserstein

wasserstein_distances
all-pairspairedcondensedsingledistancePoint sets
\[ W_p(X,Y)=\left(\min_{\pi\in\Pi(a,b)}\sum_{i,j}\pi_{ij}\|x_i-y_j\|_2^p\right)^{1/p} \]

Uniform masses; p=1 or 2. Exact discrete solve for at most 4096 positive-mass point pairs, with CPU solver coordination even for CUDA inputs.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairswasserstein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: wasserstein.

Accepted metric options: p, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "wasserstein_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "wasserstein_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Energy Distance

energy_distances
all-pairspairedcondensedsingledistancePoint sets
\[ E(X,Y)=\sqrt{\max\{2\mathbb E\|X-Y\|_2-\mathbb E\|X-X^{\prime}\|_2-\mathbb E\|Y-Y^{\prime}\|_2,0\}} \]

Uniform empirical masses. Includes within-cloud diagonals; squared=True returns the clamped expression without the root.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsenergy_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: energy.

Accepted metric options: squared, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "energy_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "energy_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Hausdorff

hausdorff_distances
all-pairspairedcondensedsingledistancePoint sets
\[ H(X,Y)=\max\{h(X,Y),h(Y,X)\} \]

Unordered finite point sets. Different point counts and valid prefix lengths are supported.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairshausdorff_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: hausdorff.

Accepted metric options: shuffle, generator, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "hausdorff_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "hausdorff_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Point sets and transport

Kernel MMD Distance

mmd_distances
all-pairspairedcondensedsingledistancePoint sets
\[ \operatorname{MMD}(X,Y)=\sqrt{\max\{\mathbb E k(X,X^{\prime})+\mathbb E k(Y,Y^{\prime})-2\mathbb E k(X,Y),0\}} \]

Biased empirical expectations include within-cloud diagonals. squared=True omits the root. Kernel selection and scale matter; historical laplacian uses Euclidean ground while laplacian_kernel uses Manhattan ground.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmmd_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: mmd.

Accepted metric options: squared, kernel, length_scale, gamma, degree, coef0, alpha, nu, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "mmd_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "mmd_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Point sets and transport

Sinkhorn Divergence

sinkhorn_divergences
all-pairspairedcondensedsingledistancePoint sets
\[ S_\varepsilon(X,Y)=F_\varepsilon(X,Y)-\tfrac12F_\varepsilon(X,X)-\tfrac12F_\varepsilon(Y,Y),\quad F_\varepsilon=\min_{\pi\in\Pi(a,b)}\langle\pi,C\rangle+\varepsilon\operatorname{KL}(\pi\Vert a\otimes b) \]

Cij=||xi−yj||₂^p, p=1 or 2. Fixed-budget log-domain updates, regularization=0.1 and max_iter=100. Full KL-regularized objective, not transport-only cost. Finite-budget results may be negative; no root.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssinkhorn_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: sinkhorn, sinkhorn_distances.

Accepted metric options: p, regularization, max_iter, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "sinkhorn_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "sinkhorn_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Sliced Wasserstein

sliced_wasserstein_distances
all-pairspairedcondensedsingledistancePoint sets
\[ \widehat{SW}_p(X,Y)=\left[\frac1L\sum_{\ell=1}^L\int_0^1|F^{-1}_{X\theta_\ell}(t)-F^{-1}_{Y\theta_\ell}(t)|^p\,dt\right]^{1/p} \]

Explicit shared unit projections [D,L] are required; p=1 or 2. Handles unequal point counts by exact projected quantile integration. This is sliced, not full-dimensional Wp.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssliced_wasserstein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: sliced_wasserstein.

Accepted metric options: p, projections, x1_lengths, x2_lengths, chunk_size, batched, feature_dim. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "sliced_wasserstein_distances"
options = dict(projections=torch.eye(2))
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "sliced_wasserstein_distances";
tp::ComparisonOptions options;
options.projections = at::eye(2);
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Point sets and transport

Squared MMD Statistic

mmd_statistics
all-pairspairedcondensedsinglestatisticPoint sets
\[ \widehat{\operatorname{MMD}}_u^2=\frac{\sum_{i\ne j}k(x_i,x_j)}{P(P-1)}+\frac{\sum_{i\ne j}k(y_i,y_j)}{Q(Q-1)}-\frac{2\sum_{i,j}k(x_i,y_j)}{PQ} \]

estimator="unbiased" is the default and requires at least two valid points on each side. estimator="biased" includes diagonals. Signed result: no clamp or root.

Shapes and Modes

InputLayout
One sample[P,D]
Collection X[...,N,P,D]
Collection Y[...,M,Q,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmmd_statistics
Pairedpaired_mmd_statistics
Condensedcondensed_mmd_statistics

The Python and C++ compare selectors cover every mode shown above. Use compare for this statistic; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: mmd_statistic.

Accepted metric options: kernel, estimator, length_scale, gamma, degree, coef0, alpha, nu, batched, feature_dim, x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 3, 2)  # [N=2,P=3,D=2]
Y = torch.rand(3, 4, 2)  # [M=3,Q=4,D=2]

metric = "mmd_statistics"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 3, 2});  // [N=2,P=3,D=2]
auto Y = at::rand({3, 4, 2});  // [M=3,Q=4,D=2]

const std::string metric = "mmd_statistics";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this statistic for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Metric reference

Kernels

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Additive Chi-Squaredkernelall_pairs, paired, condensed, single
Bhattacharyya Coefficientkernelall_pairs, paired, condensed, single
Chi-Squared Kernelkernelall_pairs, paired, condensed, single
Cosine Similaritykernelall_pairs, paired, condensed, single
Gaussian-Mixture Overlapkernelall_pairs, paired, condensed, single
Histogram Intersectionkernelall_pairs, paired, condensed, single
Laplacian Kernelkernelall_pairs, paired, condensed, single
Linear Kernelkernelall_pairs, paired, condensed, single
Matérnkernelall_pairs, paired, condensed, single
Polynomial Kernelkernelall_pairs, paired, condensed, single
RBF / Gaussian Kernelkernelall_pairs, paired, condensed, single
Rational Quadratickernelall_pairs, paired, condensed, single
Sigmoid Similaritykernelall_pairs, paired, condensed, single
Kernels

Additive Chi-Squared

additive_chi2_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=-\sum_i\frac{(x_i-y_i)^2}{x_i+y_i} \]

Nonnegative coordinates; joint zeros contribute zero. Uses the negative additive chi-squared convention.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsadditive_chi2_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: additive_chi2.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "additive_chi2_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "additive_chi2_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Bhattacharyya Coefficient

bhattacharyya_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(p,q)=\sum_i\sqrt{p_iq_i} \]

Normalizes nonnegative positive-mass rows. This is the coefficient; its negative logarithm is the Bhattacharyya distance.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsbhattacharyya_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: bhattacharyya.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "bhattacharyya_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "bhattacharyya_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Kernels

Chi-Squared Kernel

chi2_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\exp\!\left[-\gamma\sum_i\frac{(x_i-y_i)^2}{x_i+y_i}\right] \]

Nonnegative coordinates; joint zeros contribute zero. gamma defaults to 1.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairschi2_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: chi2.

Accepted metric options: gamma. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "chi2_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "chi2_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Cosine Similarity

cosine_similarity
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\frac{x^\mathsf{T}y}{\|x\|_2\|y\|_2} \]

Zero-norm cross comparisons return zero. The short cosine alias denotes similarity only inside kernel selectors.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairscosine_similarity

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: cosine, cosine_kernel.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "cosine_similarity"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "cosine_similarity";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Gaussian-Mixture Overlap

gaussian_mixture_overlap_kernel
all-pairspairedcondensedsinglekernelMixtures
\[ K(p,q)=\int p(z)q(z)\,dz=\sum_{i,j}a_i b_j\,\mathcal N(\mu_i;\nu_j,\Sigma_i+\Lambda_j) \]

Normalized nonnegative weights and SPD covariances. Exact density overlap, without learned models.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_overlap_kernel
Pairedpaired_gaussian_mixture_overlap_kernel
Condensedcondensed_gaussian_mixture_overlap_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_overlap.

Accepted metric options: x1_lengths, x2_lengths, chunk_size, component_chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_overlap_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_overlap_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Kernels

Histogram Intersection

histogram_intersection_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\sum_i\min(x_i,y_i) \]

Raw nonnegative masses, without normalization. Self similarity equals total mass; ties split gradients equally.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairshistogram_intersection_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: histogram_intersection.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "histogram_intersection_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "histogram_intersection_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Kernels

Laplacian Kernel

laplacian_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\exp(-\gamma\|x-y\|_1) \]

Uses Manhattan distance, with gamma=1/D by default. Distinct from Matérn nu=0.5 in multiple dimensions.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslaplacian_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: laplacian.

Accepted metric options: gamma. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "laplacian_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "laplacian_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Linear Kernel

linear_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=x^\mathsf{T}y \]

Raw dot product; no automatic centering or normalization.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslinear_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: linear.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "linear_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "linear_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Matérn

matern_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k_{1/2}=e^{-r},\quad k_{3/2}=(1+\sqrt3r)e^{-\sqrt3r},\quad k_{5/2}=(1+\sqrt5r+5r^2/3)e^{-\sqrt5r},\quad r=\|x-y\|_2/\ell \]

nu is 0.5, 1.5 (default) or 2.5. Positive scalar length_scale defaults to 1.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmatern_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: matern.

Accepted metric options: length_scale, nu. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "matern_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "matern_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Polynomial Kernel

polynomial_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=(\gamma x^\mathsf{T}y+c_0)^d \]

degree defaults to 3, gamma to 1/D and coef0 to 1.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairspolynomial_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: polynomial.

Accepted metric options: degree, gamma, coef0. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "polynomial_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "polynomial_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

RBF / Gaussian Kernel

rbf_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\exp(-\gamma\|x-y\|_2^2) \]

gamma defaults to 1/D; use explicit gamma to match another kernel parameterization.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsrbf_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: rbf.

Accepted metric options: gamma. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "rbf_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "rbf_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Rational Quadratic

rational_quadratic_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\left(1+\frac{\|x-y\|_2^2}{2\alpha\ell^2}\right)^{-\alpha} \]

Positive scalar length_scale=1 and alpha=1 by default; isotropic and unit amplitude.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsrational_quadratic_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: rational_quadratic.

Accepted metric options: length_scale, alpha. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "rational_quadratic_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "rational_quadratic_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Kernels

Sigmoid Similarity

sigmoid_kernel
all-pairspairedcondensedsinglekernelVectors
\[ k(x,y)=\tanh(\gamma x^\mathsf{T}y+c_0) \]

gamma defaults to 1/D and coef0 to 1. Not positive-semidefinite for arbitrary parameters.

Shapes and Modes

InputLayout
One sample[D]
Collection X[...,N,D]
Collection Y[...,M,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssigmoid_kernel

The Python and C++ compare selectors cover every mode shown above. pairwise_kernels, paired_kernels and condensed_kernels also select it. single_comparison is shorthand for single mode.

Catalog aliases: sigmoid.

Accepted metric options: gamma, coef0. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 2) + 0.2  # [N=2,D=2]
Y = torch.rand(3, 2) + 0.2  # [M=3,D=2]

metric = "sigmoid_kernel"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 2}) + 0.2;  // [N=2,D=2]
auto Y = at::rand({3, 2}) + 0.2;  // [M=3,D=2]

const std::string metric = "sigmoid_kernel";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this kernel, with its feature-space geometry methods, for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Metric reference

Gaussians and Covariances

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Affine-Invariant Riemanniandistanceall_pairs, paired, condensed, single
Gaussian Wasserstein / Buresdistanceall_pairs, paired, condensed, single
Log-Euclideandistanceall_pairs, paired, condensed, single
Stein / Jensen-Bregman LogDetdistanceall_pairs, paired, condensed, single
Gaussians and covariances

Affine-Invariant Riemannian

affine_invariant_distances
all-pairspairedcondensedsingledistanceCovariances
\[ d(A,B)=\|\log(A^{-1/2}BA^{-1/2})\|_F \]

Symmetric positive-definite matrices. squared=True returns the squared distance. No jitter or eigenvalue clipping.

Shapes and Modes

InputLayout
One sample[D,D]
Collection X[...,N,D,D]
Collection Y[...,M,D,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsaffine_invariant_distances
Pairedpaired_affine_invariant_distances
Condensedcondensed_affine_invariant_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: affine_invariant.

Accepted metric options: squared. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.eye(2).repeat(2, 1, 1)      # [N=2,D=2,D=2]
Y = 2 * torch.eye(2).repeat(3, 1, 1)  # [M=3,D=2,D=2]

metric = "affine_invariant_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::eye(2).repeat({2, 1, 1});
auto Y = 2 * at::eye(2).repeat({3, 1, 1});

const std::string metric = "affine_invariant_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussians and covariances

Gaussian Wasserstein / Bures

gaussian_wasserstein_distances
all-pairspairedcondensedsingledistanceGaussians
\[ W_2^2=\|\mu-\nu\|_2^2+\operatorname{tr}\!\left(\Sigma+\Lambda-2(\Sigma^{1/2}\Lambda\Sigma^{1/2})^{1/2}\right) \]

Means and finite symmetric positive-semidefinite covariances. Default returns W2; squared=True returns the expression shown. Singular covariance boundaries use a finite derivative continuation.

Shapes and Modes

InputLayout
One sample(mean[D], covariance[D,D])
Collection X(means[...,N,D], covariances[...,N,D,D])
Collection Y(means[...,M,D], covariances[...,M,D,D])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_wasserstein_distances
Pairedpaired_gaussian_wasserstein_distances
Condensedcondensed_gaussian_wasserstein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_wasserstein.

Accepted metric options: squared. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2), torch.eye(2).repeat(2, 1, 1))
Y = (torch.rand(3, 2), 2 * torch.eye(2).repeat(3, 1, 1))
# (means [N,D], covariances [N,D,D])

metric = "gaussian_wasserstein_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2}), at::eye(2).repeat({2, 1, 1})};
tp::ComparisonCollection Y{at::rand({3, 2}), 2 * at::eye(2).repeat({3, 1, 1})};

const std::string metric = "gaussian_wasserstein_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussians and covariances

Log-Euclidean

log_euclidean_distances
all-pairspairedcondensedsingledistanceCovariances
\[ d(A,B)=\|\log A-\log B\|_F \]

Symmetric positive-definite matrices; principal matrix logarithm. squared=True returns the squared Frobenius distance.

Shapes and Modes

InputLayout
One sample[D,D]
Collection X[...,N,D,D]
Collection Y[...,M,D,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslog_euclidean_distances
Pairedpaired_log_euclidean_distances
Condensedcondensed_log_euclidean_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: log_euclidean.

Accepted metric options: squared. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.eye(2).repeat(2, 1, 1)      # [N=2,D=2,D=2]
Y = 2 * torch.eye(2).repeat(3, 1, 1)  # [M=3,D=2,D=2]

metric = "log_euclidean_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::eye(2).repeat({2, 1, 1});
auto Y = 2 * at::eye(2).repeat({3, 1, 1});

const std::string metric = "log_euclidean_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussians and covariances

Stein / Jensen-Bregman LogDet

stein_distances
all-pairspairedcondensedsingledistanceCovariances
\[ d(A,B)=\sqrt{\log\det\frac{A+B}{2}-\tfrac12\log\det A-\tfrac12\log\det B} \]

Symmetric positive-definite matrices. squared=True returns the divergence inside the root, not its square.

Shapes and Modes

InputLayout
One sample[D,D]
Collection X[...,N,D,D]
Collection Y[...,M,D,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsstein_distances
Pairedpaired_stein_distances
Condensedcondensed_stein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: stein.

Accepted metric options: squared. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.eye(2).repeat(2, 1, 1)      # [N=2,D=2,D=2]
Y = 2 * torch.eye(2).repeat(3, 1, 1)  # [M=3,D=2,D=2]

metric = "stein_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::eye(2).repeat({2, 1, 1});
auto Y = 2 * at::eye(2).repeat({3, 1, 1});

const std::string metric = "stein_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Metric reference

Gaussian Mixtures

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Gaussian-Mixture Cauchy-Schwarzdistanceall_pairs, paired, condensed, single
Gaussian-Mixture Entropic Transportdistanceall_pairs, paired, condensed, single
Gaussian-Mixture L2distanceall_pairs, paired, condensed, single
Gaussian-Mixture Sinkhorndistanceall_pairs, paired, condensed, single
Gaussian-Mixture Wassersteindistanceall_pairs, paired, condensed, single
Gaussian mixtures

Gaussian-Mixture Cauchy-Schwarz

gaussian_mixture_cauchy_schwarz_divergences
all-pairspairedcondensedsingledistanceMixtures
\[ D_{\mathrm{CS}}(p,q)=-\log\frac{K(p,q)}{\sqrt{K(p,p)K(q,q)}} \]

Uses Gaussian density overlap K and SPD covariances. Natural logarithm, no factor of two; stable log-overlap evaluation.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_cauchy_schwarz_divergences
Pairedpaired_gaussian_mixture_cauchy_schwarz_divergences
Condensedcondensed_gaussian_mixture_cauchy_schwarz_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_cauchy_schwarz.

Accepted metric options: x1_lengths, x2_lengths, chunk_size, component_chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_cauchy_schwarz_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_cauchy_schwarz_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussian mixtures

Gaussian-Mixture Entropic Transport

gaussian_mixture_entropic_transport_costs
all-pairspairedcondensedsingledistanceMixtures
\[ F_\varepsilon(p,q)=\min_{\pi\in\Pi(a,b)}\langle\pi,C\rangle+\varepsilon\operatorname{KL}(\pi\Vert a\otimes b),\quad C_{ij}=W_2^2(\mathcal N_i,\mathcal N_j) \]

PSD covariances; finite-iteration estimate of the full regularized objective. regularization=0.1, max_iter=100. Includes entropy; self costs need not vanish.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_entropic_transport_costs
Pairedpaired_gaussian_mixture_entropic_transport_costs
Condensedcondensed_gaussian_mixture_entropic_transport_costs

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_entropic_transport.

Accepted metric options: regularization, max_iter, x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_entropic_transport_costs"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_entropic_transport_costs";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussian mixtures

Gaussian-Mixture L2

gaussian_mixture_l2_distances
all-pairspairedcondensedsingledistanceMixtures
\[ d(p,q)=\sqrt{K(p,p)+K(q,q)-2K(p,q)} \]

Uses Gaussian density overlap K and SPD covariances. squared=True omits the root; negative roundoff residuals are clamped to zero.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_l2_distances
Pairedpaired_gaussian_mixture_l2_distances
Condensedcondensed_gaussian_mixture_l2_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_l2.

Accepted metric options: squared, x1_lengths, x2_lengths, chunk_size, component_chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_l2_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_l2_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussian mixtures

Gaussian-Mixture Sinkhorn

gaussian_mixture_sinkhorn_divergences
all-pairspairedcondensedsingledistanceMixtures
\[ S_\varepsilon(p,q)=F_\varepsilon(p,q)-\tfrac12F_\varepsilon(p,p)-\tfrac12F_\varepsilon(q,q) \]

Gaussian component W2² ground costs and PSD covariances. Fixed-budget regularized updates; no root or nonnegative clamp. Finite-budget estimates may be negative.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_sinkhorn_divergences
Pairedpaired_gaussian_mixture_sinkhorn_divergences
Condensedcondensed_gaussian_mixture_sinkhorn_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_sinkhorn.

Accepted metric options: regularization, max_iter, x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_sinkhorn_divergences"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_sinkhorn_divergences";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Gaussian mixtures

Gaussian-Mixture Wasserstein

gaussian_mixture_wasserstein_distances
all-pairspairedcondensedsingledistanceMixtures
\[ MW_2(p,q)=\sqrt{\min_{\pi\in\Pi(a,b)}\sum_{i,j}\pi_{ij}W_2^2(\mathcal N_i,\mathcal N_j)} \]

PSD covariances. Restricts transport to Gaussian component couplings: not exact W2 between full mixture densities. At most 4096 positive-mass component pairs; CPU-controlled solve. squared=True omits the root.

Shapes and Modes

InputLayout
One sample(means[K,D], covariances[K,D,D], weights[K])
Collection X(means[...,N,K,D], covariances[...,N,K,D,D], weights[...,N,K])
Collection Y(means[...,M,L,D], covariances[...,M,L,D,D], weights[...,M,L])
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsgaussian_mixture_wasserstein_distances
Pairedpaired_gaussian_mixture_wasserstein_distances
Condensedcondensed_gaussian_mixture_wasserstein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: gaussian_mixture_wasserstein.

Accepted metric options: squared, x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = (torch.rand(2, 2, 2), torch.eye(2).repeat(2, 2, 1, 1), torch.ones(2, 2))
Y = (torch.rand(3, 2, 2), torch.eye(2).repeat(3, 2, 1, 1), torch.ones(3, 2))
# (means [N,K,D], covariances [N,K,D,D], weights [N,K]); K=D=2

metric = "gaussian_mixture_wasserstein_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = tuple(t[:2] for t in Y)
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = tuple(t[0] for t in X), tuple(t[0] for t in Y)
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
tp::ComparisonCollection X{at::rand({2, 2, 2}),
    at::eye(2).repeat({2, 2, 1, 1}), at::ones({2, 2})};
tp::ComparisonCollection Y{at::rand({3, 2, 2}),
    at::eye(2).repeat({3, 2, 1, 1}), at::ones({3, 2})};

const std::string metric = "gaussian_mixture_wasserstein_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
tp::ComparisonCollection paired_Y{Y[0].narrow(0, 0, 2), Y[1].narrow(0, 0, 2), Y[2].narrow(0, 0, 2)};
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
tp::ComparisonCollection x{X[0][0], X[1][0], X[2][0]};
tp::ComparisonCollection y{Y[0][0], Y[1][0], Y[2][0]};
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Metric reference

Images

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Multiscale SSIMscoreall_pairs, paired, condensed, single
Peak Signal-to-Noise Ratioscoreall_pairs, paired, condensed, single
Structural Similarity / SSIMscoreall_pairs, paired, condensed, single
Images

Multiscale SSIM

multiscale_ssim_scores
all-pairspairedcondensedsinglescoreImages
\[ \operatorname{MS\!SSIM}=\operatorname{mean}_c\!\left[\prod_{j<L}\max(\overline{cs}_{j,c},0)^{w_j}\max(\overline{l\,cs}_{L,c},0)^{w_L}\right] \]

SSIM luminance l and contrast/structure cs terms across average-pooled scales. data_range required. Weights are not renormalized; nonpositive factors contribute zero. Match padding/pooling conventions when comparing libraries.

Shapes and Modes

InputLayout
One sample[C,H,W]
Collection X[...,N,C,H,W]
Collection Y[...,M,C,H,W]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmultiscale_ssim_scores
Pairedpaired_multiscale_ssim_scores
Condensedcondensed_multiscale_ssim_scores

The Python and C++ compare selectors cover every mode shown above. Use compare for this score; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: multiscale_ssim.

Accepted metric options: data_range, window_size, sigma, k1, k2, weights, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 8, 8)  # [N=2,C=1,H=8,W=8]
Y = torch.rand(3, 1, 8, 8)  # [M=3,C=1,H=8,W=8]

metric = "multiscale_ssim_scores"
options = dict(data_range=1.0, window_size=3, weights=[1.0])
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 8, 8});
auto Y = at::rand({3, 1, 8, 8});

const std::string metric = "multiscale_ssim_scores";
tp::ComparisonOptions options;
options.data_range = 1.0;
options.window_size = 3;
options.weights = std::vector<double>{1.0};
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this score for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Images

Peak Signal-to-Noise Ratio

psnr_scores
all-pairspairedcondensedsinglescoreImages
\[ \operatorname{PSNR}(x,y)=10\log_{10}\frac{R^2}{(CHW)^{-1}\sum_{c,h,w}(x_{chw}-y_{chw})^2} \]

Aligned whole images; data_range R is required. Returns dB. Identical images give +infinity with a chosen zero derivative.

Shapes and Modes

InputLayout
One sample[C,H,W]
Collection X[...,N,C,H,W]
Collection Y[...,M,C,H,W]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairspsnr_scores
Pairedpaired_psnr_scores
Condensedcondensed_psnr_scores

The Python and C++ compare selectors cover every mode shown above. Use compare for this score; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: psnr.

Accepted metric options: data_range, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 8, 8)  # [N=2,C=1,H=8,W=8]
Y = torch.rand(3, 1, 8, 8)  # [M=3,C=1,H=8,W=8]

metric = "psnr_scores"
options = dict(data_range=1.0)
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 8, 8});
auto Y = at::rand({3, 1, 8, 8});

const std::string metric = "psnr_scores";
tp::ComparisonOptions options;
options.data_range = 1.0;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this score for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Images

Structural Similarity / SSIM

ssim_scores
all-pairspairedcondensedsinglescoreImages
\[ \operatorname{SSIM}=\operatorname{mean}_{c,h,w}\!\left[\frac{2\mu_x\mu_y+C_1}{\mu_x^2+\mu_y^2+C_1}\frac{2\sigma_{xy}+C_2}{\sigma_x^2+\sigma_y^2+C_2}\right] \]

Aligned images; data_range required, C1=(k1 R)² and C2=(k2 R)². Gaussian-weighted population moments with valid windows; negative scores retained.

Shapes and Modes

InputLayout
One sample[C,H,W]
Collection X[...,N,C,H,W]
Collection Y[...,M,C,H,W]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsssim_scores
Pairedpaired_ssim_scores
Condensedcondensed_ssim_scores

The Python and C++ compare selectors cover every mode shown above. Use compare for this score; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: ssim, ssim_index.

Accepted metric options: data_range, window_size, sigma, k1, k2, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 8, 8)  # [N=2,C=1,H=8,W=8]
Y = torch.rand(3, 1, 8, 8)  # [M=3,C=1,H=8,W=8]

metric = "ssim_scores"
options = dict(data_range=1.0, window_size=3)
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 8, 8});
auto Y = at::rand({3, 1, 8, 8});

const std::string metric = "ssim_scores";
tp::ComparisonOptions options;
options.data_range = 1.0;
options.window_size = 3;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this score for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Metric reference

Audio

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Log-Spectral Distancedistanceall_pairs, paired, condensed, single
Multi-Resolution STFTdistanceall_pairs, paired, single
Scale-Invariant SDRscoreall_pairs, paired, single
Signal SNRscoreall_pairs, paired, single
Spectral Convergencedistanceall_pairs, paired, single
Audio

Log-Spectral Distance

log_spectral_distances
all-pairspairedcondensedsingledistanceAudio
\[ d(e,r)=\operatorname{mean}_{c,f,t}|\log\max(A,\delta)-\log\max(B,\delta)| \]

A/B are aligned STFT magnitudes. Natural logs; log_floor=1e-7. No phase or mel weighting. Different waveforms can share magnitudes.

Shapes and Modes

InputLayout
One sample[C,T]
Collection X[...,N,C,T]
Collection Y[...,M,C,T]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslog_spectral_distances
Pairedpaired_log_spectral_distances
Condensedcondensed_log_spectral_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: log_spectral.

Accepted metric options: n_fft, hop_length, win_length, window, center, pad_mode, normalized, log_floor, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 24)  # [N=2,C=1,T=24]
Y = torch.rand(3, 1, 24)  # [M=3,C=1,T=24]

metric = "log_spectral_distances"
options = dict(n_fft=8)
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 24});
auto Y = at::rand({3, 1, 24});

const std::string metric = "log_spectral_distances";
tp::ComparisonOptions options;
options.n_fft = 8;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Audio

Multi-Resolution STFT

multiresolution_stft_distances
all-pairspairedsingledistanceAudio
\[ d(e,r)=\frac1L\sum_{\ell=1}^L\left[\frac{\|A_\ell-B_\ell\|_F}{\max(\|B_\ell\|_F,\delta_n)}+\operatorname{mean}|\log\max(A_\ell,\delta_l)-\log\max(B_\ell,\delta_l)|\right] \]

Arithmetic mean across explicit FFT/hop/window resolution triples. Equal weight for convergence and log-magnitude terms. No sample-rate conversion or phase alignment.

Shapes and Modes

InputLayout
One sample[C,T]
Collection X[...,N,C,T]
Collection Y[...,M,C,T]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsmultiresolution_stft_distances
Pairedpaired_multiresolution_stft_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: multiresolution_stft.

Accepted metric options: resolutions, window, center, pad_mode, normalized, log_floor, norm_floor, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 24)  # [N=2,C=1,T=24]
Y = torch.rand(3, 1, 24)  # [M=3,C=1,T=24]

metric = "multiresolution_stft_distances"
options = dict(resolutions=[(8, 2, 8)])
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 24});
auto Y = at::rand({3, 1, 24});

const std::string metric = "multiresolution_stft_distances";
tp::ComparisonOptions options;
options.resolutions = std::vector<int64_t>{8, 2, 8};
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Audio

Scale-Invariant SDR

si_sdr_scores
all-pairspairedsinglescoreAudio
\[ s=\frac{\langle e,r\rangle}{\|r\|_2^2}r,\qquad \operatorname{SI\!SDR}=\operatorname{mean}_c10\log_{10}\frac{\max(\|s\|_2^2,\delta)}{\max(\|e-s\|_2^2,\delta)} \]

First operand is estimate e, second reference r. Per-channel dB scores are averaged. zero_mean=True and energy_floor=1e-8 by default. Zero-reference projection is zero.

Shapes and Modes

InputLayout
One sample[C,T]
Collection X[...,N,C,T]
Collection Y[...,M,C,T]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssi_sdr_scores
Pairedpaired_si_sdr_scores

The Python and C++ compare selectors cover every mode shown above. Use compare for this score; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: si_sdr.

Accepted metric options: zero_mean, energy_floor, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 24)  # [N=2,C=1,T=24]
Y = torch.rand(3, 1, 24)  # [M=3,C=1,T=24]

metric = "si_sdr_scores"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 24});
auto Y = at::rand({3, 1, 24});

const std::string metric = "si_sdr_scores";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this score for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Audio

Signal SNR

signal_snr_scores
all-pairspairedsinglescoreAudio
\[ \operatorname{SNR}=\operatorname{mean}_c10\log_{10}\frac{\max(\|r\|_2^2,\delta)}{\max(\|e-r\|_2^2,\delta)} \]

First operand is estimate e, second reference r. zero_mean=False; energy_floor=1e-8. This audio dB score differs from vector snr_distances.

Shapes and Modes

InputLayout
One sample[C,T]
Collection X[...,N,C,T]
Collection Y[...,M,C,T]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssignal_snr_scores
Pairedpaired_signal_snr_scores

The Python and C++ compare selectors cover every mode shown above. Use compare for this score; distance/kernel selectors have different roles. single_comparison is shorthand for single mode.

Catalog aliases: signal_snr.

Accepted metric options: zero_mean, energy_floor, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 24)  # [N=2,C=1,T=24]
Y = torch.rand(3, 1, 24)  # [M=3,C=1,T=24]

metric = "signal_snr_scores"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 24});
auto Y = at::rand({3, 1, 24});

const std::string metric = "signal_snr_scores";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this score for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Audio

Spectral Convergence

spectral_convergence_distances
all-pairspairedsingledistanceAudio
\[ d(e,r)=\frac{\|A-B\|_F}{\max(\|B\|_F,\delta)} \]

STFT magnitude Frobenius norms span channels/frequencies/frames. norm_floor=1e-7; reference normalization makes this directional.

Shapes and Modes

InputLayout
One sample[C,T]
Collection X[...,N,C,T]
Collection Y[...,M,C,T]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. Condensed mode is not exposed for this directional comparison. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsspectral_convergence_distances
Pairedpaired_spectral_convergence_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; condensed distance selectors reject it. single_comparison is shorthand for single mode.

Catalog aliases: spectral_convergence.

Accepted metric options: n_fft, hop_length, win_length, window, center, pad_mode, normalized, norm_floor, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 1, 24)  # [N=2,C=1,T=24]
Y = torch.rand(3, 1, 24)  # [M=3,C=1,T=24]

metric = "spectral_convergence_distances"
options = dict(n_fft=8)
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 1, 24});
auto Y = at::rand({3, 1, 24});

const std::string metric = "spectral_convergence_distances";
tp::ComparisonOptions options;
options.n_fft = 8;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Metric reference

Sequences and Tokens

Choose a comparison for its equation, input shapes, modes and Python/C++ examples.

ComparisonRoleModes
Debiased Soft-DTWdistanceall_pairs, paired, condensed, single
Dynamic Time Warpingdistanceall_pairs, paired, condensed, single
Insertion/Deletiondistanceall_pairs, paired, condensed, single
Levenshteindistanceall_pairs, paired, condensed, single
Sequences and tokens

Debiased Soft-DTW

soft_dtw_divergences
all-pairspairedcondensedsingledistanceSequences
\[ D_\gamma(x,y)=S_\gamma(x,y)-\tfrac12S_\gamma(x,x)-\tfrac12S_\gamma(y,y),\quad \operatorname{softmin}_\gamma(z)=-\gamma\log\sum_k e^{-z_k/\gamma} \]

Sγ uses the DTW recurrence with softmin. Positive gamma is required. Signed, unrooted and unclamped; no general triangle-inequality guarantee.

Shapes and Modes

InputLayout
One sample[T,D]
Collection X[...,N,T,D]
Collection Y[...,M,S,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairssoft_dtw_divergences
Pairedpaired_soft_dtw_divergences
Condensedcondensed_soft_dtw_divergences

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: soft_dtw.

Accepted metric options: gamma, x1_lengths, x2_lengths, window, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 4, 2)  # [N=2,T=4,D=2]
Y = torch.rand(3, 5, 2)  # [M=3,S=5,D=2]

metric = "soft_dtw_divergences"
options = dict(gamma=0.7)
all_pairs = tp.compare(X, Y, metric=metric, **options)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired", **options)  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed", **options)      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single", **options)  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 4, 2});
auto Y = at::rand({3, 5, 2});

const std::string metric = "soft_dtw_divergences";
tp::ComparisonOptions options;
options.gamma = 0.7;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Sequences and tokens

Dynamic Time Warping

dtw_distances
all-pairspairedcondensedsingledistanceSequences
\[ D_{ij}=\|x_i-y_j\|_2^2+\min(D_{i-1,j},D_{i,j-1},D_{i-1,j-1}),\quad d(x,y)=\sqrt{D_{T,S}} \]

Squared-Euclidean local costs; squared=True returns the minimum path sum. Optional integer window imposes |i−j|≤window. Infeasible pairs give infinity.

Shapes and Modes

InputLayout
One sample[T,D]
Collection X[...,N,T,D]
Collection Y[...,M,S,D]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsdtw_distances
Pairedpaired_dtw_distances
Condensedcondensed_dtw_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: dtw.

Accepted metric options: x1_lengths, x2_lengths, squared, window, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.rand(2, 4, 2)  # [N=2,T=4,D=2]
Y = torch.rand(3, 5, 2)  # [M=3,S=5,D=2]

metric = "dtw_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::rand({2, 4, 2});
auto Y = at::rand({3, 5, 2});

const std::string metric = "dtw_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Floating CPU/CUDA inputs use matching float32 / float64, with lower precision promoted under autocast where supported. First derivatives follow the metric’s smooth and boundary conventions. See the detailed conventions and precision and deployment.

Sequences and tokens

Insertion/Deletion

indel_distances
all-pairspairedcondensedsingledistanceTokens
\[ d(x,y)=|x|+|y|-2\operatorname{LCS}(x,y) \]

Integer tokens. Insertion/deletion cost one, substitution cost two. No normalization by length; int64 output.

Shapes and Modes

InputLayout
One sample[T]
Collection X[...,N,T]
Collection Y[...,M,S]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairsindel_distances
Pairedpaired_indel_distances
Condensedcondensed_indel_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: indel.

Accepted metric options: x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[1, 2, 3], [3, 2, 1]])             # [N=2,T=3]
Y = torch.tensor([[1, 2, 2], [3, 3, 1], [2, 2, 1]])  # [M=3,S=3]

metric = "indel_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({1, 2, 3, 3, 2, 1}, at::kLong).reshape({2, 3});
auto Y = at::tensor({1, 2, 2, 3, 3, 1, 2, 2, 1}, at::kLong).reshape({3, 3});

const std::string metric = "indel_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Token inputs are integer tensors with integer outputs; no input derivatives. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Sequences and tokens

Levenshtein

levenshtein_distances
all-pairspairedcondensedsingledistanceTokens
\[ L_{ij}=\min\{L_{i-1,j}+1,L_{i,j-1}+1,L_{i-1,j-1}+\mathbf1[x_i\ne y_j]\} \]

Integer token IDs, unit insertion/deletion/substitution costs. Returns int64; valid prefix lengths may be zero. Tokens are labels, not numeric features.

Shapes and Modes

InputLayout
One sample[T]
Collection X[...,N,T]
Collection Y[...,M,S]
ModeSelectorOutput
all-pairscompare(X,Y,metric=...)[...,N,M]
pairedcompare(X,Y,metric=...,mode="paired")[...,N]; equal sample counts
condensedcompare(X,metric=...,mode="condensed")[...,N*(N-1)//2]; strict upper triangle
singlecompare(x,y,metric=...,mode="single")[...]; leading batches only

Leading batch dimensions broadcast. See mode and shape conventions.

Entry Points

Named Python entryFunction
All-pairslevenshtein_distances
Pairedpaired_levenshtein_distances
Condensedcondensed_levenshtein_distances

The Python and C++ compare selectors cover every mode shown above. cdist / pairwise_distances and paired_distances select this comparison; pdist / condensed_distances select its condensed mode. single_comparison is shorthand for single mode.

Catalog aliases: levenshtein.

Accepted metric options: x1_lengths, x2_lengths, chunk_size. Omitted options keep the metric defaults; unused options are rejected.

Example

Choose Python or C++ in the top bar; the setting applies across this guide.

Python
import torch
import torchpairwise as tp

X = torch.tensor([[1, 2, 3], [3, 2, 1]])             # [N=2,T=3]
Y = torch.tensor([[1, 2, 2], [3, 3, 1], [2, 2, 1]])  # [M=3,S=3]

metric = "levenshtein_distances"
all_pairs = tp.compare(X, Y, metric=metric)  # [2,3]
paired_Y = Y[:2]
paired = tp.compare(X, paired_Y, metric=metric, mode="paired")  # [2]
condensed = tp.compare(X, metric=metric, mode="condensed")      # [1]
x, y = X[0], Y[0]
single = tp.compare(x, y, metric=metric, mode="single")  # scalar
C++ / LibTorch
#include <torchpairwise.h>

namespace tp = torchpairwise::ops;
auto X = at::tensor({1, 2, 3, 3, 2, 1}, at::kLong).reshape({2, 3});
auto Y = at::tensor({1, 2, 2, 3, 3, 1, 2, 2, 1}, at::kLong).reshape({3, 3});

const std::string metric = "levenshtein_distances";
tp::ComparisonOptions options;
auto all_pairs = tp::compare(X, Y, metric, "all_pairs", options);  // [2,3]
auto paired_Y = Y.narrow(0, 0, 2);
auto paired = tp::compare(X, paired_Y, metric, "paired", options);  // [2]
// [1]
auto condensed = tp::compare(X, c10::nullopt, metric, "condensed", options);
auto x = X[0], y = Y[0];
auto single = tp::compare(x, y, metric, "single", options);  // scalar

Streamed Evaluation

tp.lazy(x, y, name) binds this distance for reductions, selection and products without retaining its full generated comparison matrix. Native source factories depend on the sample family; see streamed operators and C++ use.

Numerical Contract

Token inputs are integer tensors with integer outputs; no input derivatives. See the detailed conventions and precision and deployment.

Related APIs can differ in reduction, normalization, units or boundary behavior; match these before comparing values.

Metric Reference

Custom Comparisons

Use a built-in comparison when its equation fits. For a user-defined formula, make_pairwise turns a scalar two-sample function into all-pairs, paired, condensed and single comparisons. Pass the same object to tp.lazy(x, y, metric) to bind the formula to the scalar streaming engine.

For learned representations, separate encoding from comparison. The caller supplies the encoder and its loaded weights; make_feature_pairwise composes one encoder, or separate query and reference towers, with an explicit head. Extracted features can feed built-in metrics, custom metrics and streamed operators.

ExtensionWhat the User ProvidesWhat TorchPairwise Reuses
User-defined metricA scalar or broadcast-tile forward, optionally a custom VJP, autograd function or nn.ModulePair indexing, modes, tiling, scalar algebra and consumers
Native metricRegistered paired-tile forward/VJP operatorsThe same source and consumer engine in Python and LibTorch
Encoded featuresOne encoder or a query/reference pair, and a comparison headEncoding once before comparison broadcasting; explicit preparation for reuse

These extensions do not add entries to the built-in comparison catalog. Python callbacks and encoders keep their own compilation and deployment requirements.

Custom Comparisons

User-Defined Metrics

Define one comparison between intact samples. A vector sample is [D], a point set [P,D] and an image [C,H,W]; sample_ndim counts those trailing axes. The function returns one scalar Tensor. Different sample extents are allowed when the function supports them. Example: weighted squared distance. This is one possible formula; the adapter accepts any scalar comparison satisfying the callback contract, including asymmetric similarities.

\[ f(x,y;w)=\sum_d w_d(x_d-y_d)^2 \]
Python
import torch
import torchpairwise as tp

weight = torch.nn.Parameter(torch.ones(4))
def forward(x, y, w):
    return ((x - y).square() * w).sum()

metric = tp.make_pairwise(forward, parameters=(weight,), symmetric=True)
X = torch.randn(3, 4)
Y = torch.randn(5, 4)
matrix = metric(X, Y)                      # [3,5]
paired = metric(X, Y[:3], mode="paired")   # [3]
condensed = metric(X, mode="condensed")    # [3]
value = metric(X[0], Y[0], mode="single")  # scalar
C++ / LibTorch, No User Registration
#include <torchpairwise.h>
namespace tp = torchpairwise::ops;

auto weight = at::ones({4}).set_requires_grad(true);
tp::CustomComparisonOptions options;
options.parameters = {weight};
options.symmetric = true;
auto metric = tp::make_pairwise(
    [](const at::Tensor& x, const at::Tensor& y, at::TensorList p) {
      return ((x - y).square() * p[0]).sum();
    }, options);
auto X = at::randn({3, 4});
auto Y = at::randn({5, 4});
auto matrix = metric(X, Y);                             // [3,5]
auto paired = metric(X, Y.slice(0, 0, 3), "paired");    // [3]
auto condensed = metric(X, c10::nullopt, "condensed");  // [3]
auto value = metric(X[0], Y[0], "single");              // scalar

A factory mode supplies the default; a call can override it. Condensed mode requires an explicit symmetric=True assertion and takes one collection. Both paired and single modes require the second input. Symmetry is not inferred.

ModeInputs for One Trailing Sample Shape SOutput
all-pairs[...,N,*Sx], [...,M,*Sy][...,N,M]
paired[...,N,*Sx], [...,N,*Sy][...,N]
condensed[...,N,*S][...,N*(N-1)//2]
single[...,*Sx], [...,*Sy][...]; scalar without batches

Leading batches broadcast. Explicit parameters are shared tensors with the input dtype/device; they are not interpreted as additional batch dimensions. Inputs and scalar outputs use real floating dtypes. output_dtype declares a different result dtype; the callback must return it. No implicit cast is added.

Supply a Backward Formula

Ordinary PyTorch forwards use autograd. An optional backward supplies a vector-Jacobian product: given the upstream scalar gradient, return gradients for x, y and each explicit parameter, in that order.

def backward(g, x, y, w):
    delta = x - y
    return (2 * g * w * delta,
            -2 * g * w * delta,
            g * delta.square())

metric = tp.make_pairwise(forward, backward=backward,
                          parameters=(weight,), symmetric=True, compile=True)

Return a Tensor for every gradient, using zeros for unused operands. With compile=True, forward and VJP tiles compile separately. This uses no full Jacobian. Repeated samples, broadcast dimensions and shared parameter gradients accumulate according to PyTorch semantics.

Use an Autograd Function

Pass torch.autograd.Function.apply directly, without a separate backward= argument. The adapter uses the function's own backward for dense and streamed comparisons. In C++, wrap the function's apply in a lambda; the factory handles registration internally.

Python Autograd Function
import torch
import torchpairwise as tp

class SquaredDistance(torch.autograd.Function):
    generate_vmap_rule = True

    @staticmethod
    def forward(x, y):
        return (x - y).square().sum()

    @staticmethod
    def setup_context(ctx, inputs, output):
        ctx.save_for_backward(*inputs)

    @staticmethod
    def backward(ctx, grad_output):
        x, y = ctx.saved_tensors
        dx = 2 * grad_output * (x - y)
        return dx, -dx

custom = tp.make_pairwise(SquaredDistance.apply, symmetric=True, compile=True)
X = torch.randn(3, 4, requires_grad=True)
Y = torch.randn(5, 4, requires_grad=True)
values = custom(X, Y)                              # [3,5]
loss = tp.lazy(X, Y, custom, chunk_size=2).mean()  # scalar
loss.backward()                                    # populates X.grad and Y.grad
C++ Autograd Function, No User Registration
#include <torch/autograd.h>
#include <torchpairwise.h>

class SquaredDistanceFunction
    : public torch::autograd::Function<SquaredDistanceFunction> {
 public:
  static at::Tensor forward(torch::autograd::AutogradContext* ctx,
                            const at::Tensor& x, const at::Tensor& y) {
    ctx->save_for_backward({x, y});
    return (x - y).square().sum();
  }
  static std::vector<at::Tensor> backward(
      torch::autograd::AutogradContext* ctx,
      const std::vector<at::Tensor>& gradients) {
    auto inputs = ctx->get_saved_variables();
    auto dx = 2 * gradients[0] * (inputs[0] - inputs[1]);
    return {dx, -dx};
  }
};
auto custom = torchpairwise::ops::make_pairwise(
    [](const at::Tensor& x, const at::Tensor& y) {
      return SquaredDistanceFunction::apply(x, y);
    });
auto X = at::randn({3, 4}).set_requires_grad(true);
auto Y = at::randn({5, 4}).set_requires_grad(true);
auto values = custom(X, Y);                          // [3,5]
auto loss = custom.expression(X, Y).reduce("mean");  // scalar
loss.backward();                                     // fills X.grad(), Y.grad()

Python scalar callbacks are vectorized with torch.vmap, so the function needs separate forward/setup_context methods and a batching rule. Here all operations support batching, so generate_vmap_rule=True supplies it automatically. A function calling a custom native/CUDA operation may need an explicit vmap rule. Trainable shared tensors belong in parameters= and become extra apply arguments; return their gradients from the function's backward as usual.

Evaluate Whole Tiles

The default scalar form compares one pair of intact samples and relies on torch.vmap. When the formula already works with ordinary broadcasting, pass form="broadcast" to evaluate a tile at once. The function receives x:[*Lx,*S] and y:[*Ly,*S], reduces the sample_ndim sample axes itself and returns the broadcast leading shape. Cartesian tiles are [n,1,*S] versus [1,m,*S] and give [n,m]; other modes pass [P,*S] and give [P]. Broadcast callbacks need no vmap rule, and an optional backward must return gradients with exactly the shapes it was passed.

Python
cubic = tp.make_pairwise(
    lambda x, y: (x - y).abs().pow(3).sum(-1), form="broadcast")
values = cubic(X, Y)                    # [N,M]
row_sum = tp.lazy(X, Y, cubic).sum(-1)  # [N]
C++ / LibTorch
namespace tp = torchpairwise::ops;
tp::CustomComparisonOptions options;
options.broadcast = true;
auto cubic = tp::make_pairwise(
    [](const at::Tensor& x, const at::Tensor& y, at::TensorList) {
      return (x - y).abs().pow(3).sum(-1);  // [n,1,D] vs [1,m,D] -> [n,m]
    }, options);
auto values = cubic(X, Y);                                // [N,M]
auto row_sum = cubic.expression(X, Y).reduce("sum", -1);  // [N]

An nn.Module can be passed instead of a function; its parameters then become explicit operands, and parameters= must be omitted. A Python broadcast function can additionally opt in to the optional KeOps backend with keops=True. It must use only operations supported by both tensors and KeOps LazyTensors, have sample_ndim=1 and no backward. Evaluation falls back to native tiles when lowering fails, so the flag changes the backend rather than the result.

# X:[N,3], Y:[M,3], V:[M,C]
w = torch.ones(3, requires_grad=True)
weighted_rbf = tp.make_pairwise(
    lambda x, y, w: (-((x - y) * (x - y) * w).sum(-1)).exp(),
    form="broadcast", parameters=(w,), keops=True)
with tp.backend("keops"):
    out = tp.lazy(X, Y, weighted_rbf) @ V  # [N,C]; gradients reach X, Y and w

Broadcast callbacks never use the fused CUDA reducers. See the custom metric notes for the shape contract, nn.Module metrics and KeOps limits.

Stream the Same Formula

A = tp.lazy(X, Y, metric, chunk_size=2)     # logical [3,5]
row_mean = A.mean(dim=-1)                   # [3]
values, indices = A.topk(2, largest=False)  # [3,2] each
V = torch.randn(5, 2)
product = A.matmul(V)  # [3,2]

B = tp.lazy(X, Y, metric="sqeuclidean")
combined = (A + 0.2 * B).sum()  # scalar

The custom source participates in scalar algebra and the shared scalar consumers, including statistics and selections. The custom formula need not be a mathematical metric.

Forward evaluates tiles without retaining every tile's autograd graph. Backward replays one tile, computes its VJP and accumulates gradients. Parameters are packed into a shared contiguous operand per consumer call; packing costs storage proportional to parameter size, independent of N*M. Inputs, outputs, gradients and consumer-specific workspace still occupy memory.

For dense Python calls, factory chunk_size bounds evaluated pairs across all batches, default 1024. On tp.lazy, chunk_size is an independent sample tile width per axis, default 128, matching the other streaming sources. It bounds an outer tile of at most chunk_size**2 pairs, not total memory.

Keep callbacks deterministic and free of side effects so replay is meaningful. Trainable callback state must be passed through parameters; captured trainable tensors/modules are unsupported for streaming. Dense calls retain ordinary closure autograd behavior. Scalar callbacks must support vmap; compilation adds compiler requirements. Unsupported operations fail explicitly.

C++ Callables without Registration

make_pairwise accepts a lambda, function pointer or std::function. Parameter-free callbacks need only two Tensor arguments; no dispatcher boilerplate is required:

auto metric = tp::make_pairwise(
    [](const at::Tensor& x, const at::Tensor& y) {
      return (x - y).square().sum();
    });
auto values = metric(X, Y);        // [N,M]
auto A = metric.expression(X, Y);  // logical [N,M]
auto mean = A.reduce("mean", -1);  // [N]
auto product = A.matmul(V);        // [N,K], V:[M,K]

ATen operations keep their ordinary autograd behavior. A callback can also call torch::autograd::Function::apply; its backward is used naturally. Supply an optional explicit VJP when needed:

tp::CustomComparisonBackward backward = [](
    const at::Tensor& g, const at::Tensor& x, const at::Tensor& y,
    at::TensorList p) -> std::vector<at::Tensor> {
  auto delta = x - y;
  auto dx = 2 * g * p[0] * delta;
  return {dx, -dx, g * delta.square()};
};
auto metric = tp::make_pairwise(
    [](const at::Tensor& x, const at::Tensor& y, at::TensorList p) {
      return ((x - y).square() * p[0]).sum();
    }, backward, options);  // options.parameters contains weight

Shared trainable tensors belong in options.parameters, rather than captures. Other options mirror Python: mode, sample_ndim, output_dtype, chunk_size and symmetric. The returned handle stores these options and Tensor operands; rebuild it to bind different parameter tensors. Its .expression accepts separate PairwiseOperationOptions, including the independent streaming tile width.

By default, C++ scalar callbacks run once per pair inside each bounded tile. This is convenient but may launch many small GPU operations. Set options.vectorized=true when the callable already accepts paired samples x[P,*Sx], y[P,*Sy] and returns [P]. In that mode its VJP receives g[P] and returns tile-shaped input gradients and parameter gradients summed over P. There is no automatic vectorization or compilation of arbitrary C++ callbacks.

Construct once and reuse. TorchPairwise owns internal registrations and callable captures for the process lifetime so expressions and backward remain valid after the handle is destroyed. Capture long-lived state by value; referenced objects must remain alive during deferred use. Captures cannot be serialized. These are native in-process callbacks, including for streaming; use the named-operator API below when you need explicit registration identities in a deployment.

Native Configuration and Saved Context

parameters holds shared Tensor operands, including trainable weights. Fixed native configuration can be captured by value in both C++ callbacks; it does not need to be converted to a Tensor:

const int power = 3;
const bool average = false;
auto forward = [power, average](const at::Tensor& x, const at::Tensor& y,
                                at::TensorList) {
  auto terms = (x - y).abs().pow(power);
  return average ? terms.mean() : terms.sum();
};
auto backward = [power, average](const at::Tensor& g, const at::Tensor& x,
                                 const at::Tensor& y, at::TensorList)
    -> std::vector<at::Tensor> {
  auto delta = x - y;
  auto dx = g * power * delta.abs().pow(power - 1) * delta.sign();
  if (average) dx = dx / x.numel();
  return {dx, -dx};
};
auto metric = torchpairwise::ops::make_pairwise(forward, backward);

The same pattern supports double, std::string and typed configuration structs. Python callables can capture ordinary configuration in the same way. Trainable Tensors must still be explicit parameters so replay can accumulate their gradients.

For values computed during forward, use the autograd function's own context. In C++, store native values in ctx->saved_data, for example ctx->saved_data["count"] = x.numel(), and read them with ctx->saved_data["count"].toInt(). Save Tensors with save_for_backward. Python functions use setup_context and the usual context attributes/saved tensors. Keep per-call state on that context: mutating shared captures to communicate between forward and backward conflicts with tile replay and concurrent calls.

Registered C++ and CUDA Operators

Python callbacks are registered under process-local dispatcher names. They keep a Python numerical dependency. compile=True compiles their vectorized metric tile; it does not translate arbitrary Python code into a LibTorch extension.

For explicit operator identities in exported graphs, provide registered operators with this paired-tile protocol:

OperationSignature and Shapes
Forward(x[P,*Sx], y[P,*Sy], parameters:Tensor[]) -> values[P]
VJP(grad[P], x, y, parameters) -> [dx,dy,*dparameters]

Native parameter VJPs are already summed over the P pairs and match each parameter's original shape. Return defined gradients, including zeros for unused parameters. Native gradients must match the corresponding operand's dtype/device. If VJP is omitted, the registered forward must supply its own autograd behavior.

The repository's native example registers torchpairwise_example::weighted_pairs and weighted_pairs_vjp using ATen operations; they dispatch to CPU/CUDA without a bespoke CUDA kernel. Load that extension in Python, or link its registrations into the LibTorch application, before running the calls below. These example operator names are user-provided; they are not built into TorchPairwise.

Python, After Loading the User Extension
metric = tp.make_pairwise("torchpairwise_example::weighted_pairs",
    backward="torchpairwise_example::weighted_pairs_vjp",
    parameters=(weight,), symmetric=True)
result = metric(X, Y)                  # [N,M]
mean = tp.lazy(X, Y, metric).mean(-1)  # [N]
C++ / LibTorch, After Linking the User Extension
#include <torchpairwise.h>
namespace tp = torchpairwise::ops;
auto result = tp::custom_compare(X, Y, {weight},
    "torchpairwise_example::weighted_pairs",
    "torchpairwise_example::weighted_pairs_vjp");  // [N,M]
auto A = tp::custom_expression(X, Y,
    "torchpairwise_example::weighted_pairs",
    "torchpairwise_example::weighted_pairs_vjp", {weight});
auto mean = A.reduce("mean", -1);  // [N]

For a native forward with its own autograd registration, omit the VJP name (backward="" in C++). For example, register a forward whose implementation uses torch::autograd::Function::apply, or differentiable ATen operations through CompositeImplicitAutograd. The custom API then uses that registered autograd behavior during replay, without requiring a second named VJP operator. A torch::autograd::Function class alone is not a dispatcher registration.

User operator schemas, numerical implementations and autograd registrations must remain available in the target process. Export records operator names, not the implementation of a Python callback. For Python callbacks, an explicit factory name allows recreating the registration before loading a graph; it does not remove the Python requirement. Shape metadata must also be available to the chosen compilation/export frontend.

Custom streaming currently requires float32/float64 scalar outputs; the callback owns input precision and autocast behavior. First derivatives are supported by the selected function/VJP. Higher derivatives and arbitrary torch.func transforms are not a shared promise. Empty outputs do not probe the metric on fake samples.

Custom Comparisons

Encoded Feature Reuse

For comparisons that factor into an encoder and a head, compute features before forming sample pairs. Example composition:

\[ f(x,y)=g(e(x),e(y)) \]

Here \(e\) is any caller-supplied encoder and \(g\) is a compatible comparison head; neither is restricted to a particular model or distance formula. This changes the encoder workload from two encodings per pair to one per input sample. It does not separate a model that jointly processes both samples, and it does not supply pretrained models, weights or preprocessing. Python orchestration examples remain visible in either language view; the native deployment example switches between Python and C++.

Encode Before Pairing

import torch
import torchpairwise as tp

encoder = torch.nn.Sequential(torch.nn.Flatten(), torch.nn.Linear(3*8*8, 16))
images = torch.randn(3, 3, 8, 8)      # [N,C,H,W]
references = torch.randn(5, 3, 8, 8)  # [M,C,H,W]
comparison = tp.make_feature_pairwise(encoder,
    "sqeuclidean", sample_ndim=3)
values = comparison(images, references)                       # [3,5]
single = comparison(images[0], references[0], mode="single")  # scalar
StageInputOutput
Raw collection[...,N,*sample]Encoder receives [original_samples,*sample]
Encoder[original_samples,*sample][original_samples,*feature] per leaf
Restored featuresEncoded leaves[...,N,*feature]
Single mode[...,*sample][...,*feature], compared without an N/M axis
Scalar all-pairs headTwo feature collections[...,N,M]

Each original collection is encoded before comparison batch broadcasting. A [1,N,*sample] input still encodes N samples against a [B,M,*sample] input. With a single encoder, omitted Y encodes once; explicit comparison(X,X) reuses the same feature tree on both sides. Distinct tensor objects are encoded independently.

The encoder can return nonempty ordinary tuple/list/dict trees of tensor leaves. Every leaf needs the encoded count as its first axis. A head owns how those leaves become comparison inputs; a generic nested feature tree is not silently flattened into one vector.

Prepare Once, Use Several Consumers

prepared = comparison.prepare(images, references)
A = tp.lazy(prepared, chunk_size=2)  # logical [3,5]
row_mean = A.mean(-1)                # [3]
nearest = A.topk(2, largest=False)   # values/indices [3,2]
V = torch.randn(5, 4)
product = A.matmul(V)  # [3,4]
dense = prepared()     # [3,5], only when requested

Neither binding A nor its consumers reruns the encoder. Each consumer evaluates the comparison head's tiles again, reusing prepared features. Preparation is an explicit snapshot of computed features. Prepare again when the model, inputs or preprocessing should change those features. Normal comparison calls encode anew.

For several different heads, extract features directly:

features = comparison.encode(images)            # [3,16]
other_features = comparison.encode(references)  # [5,16]
cosine = tp.lazy(features, other_features, metric="cosine_distances")
rbf = tp.lazy(features, other_features, metric="rbf", gamma=0.2)

tp.lazy(images, references, comparison) encodes once and binds the head. make_feature_pairwise takes exactly one head: a string metric (positional or keyword) or head=; callable positional heads are rejected. Metric options such as gamma are allowed only with metric=. Construction mode= sets the default and call-time mode= overrides it. Built-in heads default to all-pairs; CustomComparison heads use their own default. Plain callable heads use .mode if present and otherwise receive no implicit mode.

Head kindEagerLazyKeOps / fused CUDA eligibility
Built-in metric="name"YesYesFor supported metric/consumer combinations
head=tp.make_pairwise(...)YesYes, native tile replayKeOps only for a keops=True broadcast head; no fused CUDA
Plain callable or nn.Module collection head=YesNo; use tp.make_pairwise for a pairwise functionNo

Built-in heads bind the canonical distance/kernel operator, including "rbf". Lazy bindings require all-pairs mode and a single feature tensor for metric heads. A feature tree needs an explicit head. Plain collection heads have no dense streaming fallback; wrap a pairwise function with tp.make_pairwise to stream it. A single-mode preparation cannot become a streaming matrix.

Two-Tower Retrieval

# query_encoder and reference_encoder are caller-supplied modules.
retrieval = tp.make_feature_pairwise(
    (query_encoder, reference_encoder), "sqeuclidean", sample_ndim=3)
values, indices = tp.lazy(images, references, retrieval).topk(2, largest=False)
queries = retrieval.encode(images, tower="query")
references_encoded = retrieval.encode(references, tower="reference")

The first input uses the query encoder and the second uses the reference encoder. Omitting the second input runs both towers on the first input; retrieval(images, images) also runs both. These features can be asymmetric, so condensed mode is rejected. Two-tower encode requires tower="query" or tower="reference"; a single encoder requires tower=None. Both towers obey the same chunking and model-state rules.

Training and Memory

Features remain attached to the encoder's autograd graph. A streamed head's backward accumulates gradients into feature tensors, which then flow through the encoder. Combine losses into one backward call, or follow PyTorch's normal retain_graph rules when reusing one prepared graph across backward calls.

encode_chunk_size limits original samples per encoder invocation. Comparison chunk_size instead limits sample tiles in the head. Encoder chunks can change BatchNorm, cross-sample attention and other batch-dependent behavior. Neither control bounds total feature storage or retained encoder activations. Head replay does not recompute the encoder; checkpoint it separately if the application needs that tradeoff.

The wrapper preserves model train/eval state, gradients, device, dtype and the caller's autocast context. Choose these in the application. Empty original collections are rejected before invoking arbitrary user code, since an encoder's feature structure cannot be inferred safely.

Native Deployment

The Python encoder wrapper is orchestration. In LibTorch, run the caller's loaded encoder with its chosen runtime and pass the resulting feature tensors to compare, pairwise_expression or custom_expression. These use the same native head/reduction code. Python callbacks or encoder factories do not become native merely because the head supports C++.

Python
# The caller supplies a loaded encoder.
features = encoder(images)   # [N,F]
other = encoder(references)  # [M,F]
A = tp.lazy(features, other, metric="sqeuclidean")
mean = A.mean(-1)  # [N]
C++ / LibTorch
// encoder is the caller's loaded, typed LibTorch module.
auto features = encoder->forward(images);   // [N,F]
auto other = encoder->forward(references);  // [M,F]
auto A = torchpairwise::ops::pairwise_expression(
    features, other, "sqeuclidean");
auto mean = A.reduce("mean", -1);  // [N]

See the runnable feature example. Feature reuse alone does not reproduce LPIPS/DISTS calibration or a benchmark's FID preprocessing. Learned-comparison conventions explain feature-distribution scores and caller-owned models.

Integration

Python and C++

Built-in comparisons and reductions execute native forward and first backward. Python binds tensors, metric names and options; standalone C++ calls the same algorithms without Python callbacks. Include <torchpairwise.h> and link the CMake target. To build your own extension on the installed package, see Extending from C++.

Structured Samples and Options

A Gaussian is a mean/covariance pair; a mixture adds weights. Python uses tuples, C++ uses ComparisonCollection. Explicit parameters use keyword arguments or a ComparisonOptions struct. Unused supplied options are errors.

Python
# mu:[D], covariance:[D,D], with optional leading batches
value = tp.compare((mu, covariance), (other_mu, other_covariance),
                   metric="gaussian_wasserstein_distances",
                   mode="single", squared=True)
C++ / LibTorch
tp::ComparisonOptions options;
options.squared = true;
tp::ComparisonCollection x{mu, covariance};
tp::ComparisonCollection y{other_mu, other_covariance};
auto value = tp::compare(x, y, "gaussian_wasserstein_distances",
                         "single", options);

Native Source Factories

Input familyScalarExpression factory / options
Vector/point-cloud distances and vector kernelspairwise_expression / PairwiseOperationOptions
Covariances, Gaussians, mixturesstructured_expression / StructuredOperationOptions
Imagesmedia_expression / ImageComparisonOptions, PairwiseOperationOptions
Audiomedia_expression / AudioComparisonOptions, PairwiseOperationOptions
Sequences, tokens, MMD statisticordered_expression / SequenceOperatorOptions, TokenOperatorOptions, MMDOperatorOptions
Caller matrixmatrix_expression / MatrixOperationOptions

torchpairwise::streaming re-exports these factories and adds the short forms pairwise, kernel, matrix, residual and image_map, plus the linalg and optimize solver namespaces used throughout the streamed operator guides. Newer helpers such as Sinkhorn, pair-mask constructors and LOVE live in torchpairwise::ops. Named C++ family APIs also exist. Their native symbol names are not always an identical per-metric Python name; use the mode-aware compare selector for a uniform call. See native headers and signatures and the LibTorch examples.

Compatibility

Match LibTorch's major/minor, ABI, toolchain and CUDA runtime with the library. Rebuild native consumers and re-export graphs when upgrading changed operator schemas. Generic cdist/pdist schemas include the broad catalog and collection overloads; older published binaries do not expose the same surface.

Integration

Extending from C++

A Python package that builds on torchpairwise needs only import torchpairwise. A package with its own C++ extension has two routes from the installed wheel, plus the CMake package for applications without Python.

RouteBuild needsTied to
Dispatcher calls by schema (recommended)PyTorch headers onlyoperator names and schemas
Native headerstp.get_include(), tp.library_path(), C++20this exact torchpairwise and PyTorch build

Dispatcher Calls

Every public operator is registered as torchpairwise::<name>. An extension looks it up by schema and calls it like any PyTorch operator, so autograd, CUDA and backend overrides behave as in Python. Nothing links against torchpairwise, so the extension can ship as a binary wheel; only import torchpairwise is needed before the first call.

mypkg/csrc/ops.cpp
#include <ATen/core/dispatch/Dispatcher.h>
#include <torch/library.h>

at::Tensor rbf_predict(const at::Tensor& x, const at::Tensor& centers,
                       const at::Tensor& coefficients, double gamma) {
  // Looked up on first use; the extension may load before torchpairwise.
  static const auto rbf =
      c10::Dispatcher::singleton()
          .findSchemaOrThrow("torchpairwise::rbf_kernel", "")
          .typed<at::Tensor(const at::Tensor&, const c10::optional<at::Tensor>&,
                            c10::optional<double>)>();
  return rbf.call(x, centers, gamma).matmul(coefficients);
}

TORCH_LIBRARY(mypkg, m) {
  m.def("rbf_predict(Tensor x, Tensor centers, Tensor coefficients, "
        "float gamma) -> Tensor", &rbf_predict);
}
Python
import torch
import torchpairwise  # registers torch.ops.torchpairwise
import mypkg          # loads the extension above

# Prints the schema the C++ signature must match.
print(torch.ops.torchpairwise.rbf_kernel.default._schema)
y = torch.ops.mypkg.rbf_predict(x, centers, coefficients, 0.5)
y.sum().backward()  # gradients flow through torchpairwise

Public names and schemas are the supported contract. Operators whose names start with an underscore are private. A change to a public schema is a breaking change and is listed in the migration notes.

Native Headers

The C++ API adds what a single operator call cannot express, such as streamed sources, consumers and solvers. Include <torchpairwise.h> and link the installed library:

import torchpairwise as tp
from torch.utils.cpp_extension import CppExtension

extension = CppExtension(
    'mypkg._C', ['mypkg/csrc/ops.cpp'], include_dirs=[tp.get_include()],
    extra_compile_args=['-std=c++20'], extra_link_args=[tp.library_path()])
#include <torchpairwise.h>
namespace s = torchpairwise::streaming;

s::PairwiseOptions options;
options.comparison.gamma = gamma;
options.chunk_size = 1024;  // the [N,M] kernel matrix is never formed
auto y = s::kernel(x, centers, "rbf", options).matmul(coefficients);

This route binds the extension to one installation: build it with pip install --no-build-isolation against the same PyTorch, rebuild it after upgrading torchpairwise, and do not ship it as a binary wheel. It is checked on Linux. Windows wheels ship no import library, so use dispatcher calls there.

The downstream example builds one extension of each kind and checks values and gradients against Python. Details are in the native API notes.

Integration

Precision and Deployment

Floating comparisons generally use matching float32/float64 dense CPU/CUDA tensors. Under supported CPU/CUDA autocast, half/bfloat16 inputs are promoted to float32. Direct reduced precision outside autocast is operation-specific; do not assume catalog-wide support. Boolean/equality comparisons and token sequences have separate dtype and derivative contracts.

Autograd

Most numeric metrics and scalar consumers support first derivatives. Higher order derivatives and general torch.func/vmap transforms are outside the common built-in contract. At ties, zero distances, singular covariances, empty supports and nonfinite results, derivatives follow the metric-specific conventions. Finite precision may still overflow; float64 is not an exact arithmetic mode.

Compile and Export

Metric names, modes, scalar parameters and source construction are structural. Construct operators inside forward, keep selection fixed, and use explicit .matmul() spelling for a streamed product. Built-in numerical execution does not depend on arbitrary Python callbacks. Compiled and exported graphs always use native execution, even inside a tp.backend("keops") context.

Python
class Query(torch.nn.Module):
    def forward(self, X, Y, V):
        K = tp.lazy(X, Y, metric="rbf", gamma=0.2)
        return K.matmul(V)  # X:[N,D], Y:[M,D], V:[M,C] -> [N,C]

query = torch.compile(Query(), fullgraph=True)
# torch.export.export(Query(), (X,Y,V)) captures the fixed source/consumer.
C++ / LibTorch
// The same numerical operation is available directly to LibTorch.
tp::PairwiseOperationOptions options;
options.comparison.gamma = 0.2;
auto K = tp::pairwise_expression(X, Y, "rbf", true, options);
auto output = K.matmul(V);  // [N,C]
// Export/compilation is performed by the chosen deployment frontend.

Export/save/load requires the compatible native library and its operator registrations in the target environment. Dynamic shape support depends on the source and operation; captured control flow can specialize shapes. Exact discrete transport remains CPU-controlled, including CUDA cost transfers; export does not turn that solver into a GPU-only algorithm.

See AMP/compile/export contracts for supported paths and numerical conventions for empty, Boolean and boundary behavior. TorchScript compatibility is optional.

Integration

Benchmarking

Compare equivalent formulas, output shapes, dtypes, gradient work and hardware. Streaming trades intermediate storage for tiled execution and sometimes replay; it is not a universal throughput improvement.

The public harness validates values and first gradients before timing. Cases cover distance sums, RBF products, distance quantiles, kernel regression and scaled dot-product attention. Variants include eager and fullgraph-compiled PyTorch, native dense named functions, streamed tp.lazy reductions and products, and event streaming where equivalent.

python examples/benchmark_operators.py --case rbf-matmul --device cuda   --n 2048 --m 4096 --d 32 --channels 16 --chunk-size 128   --backward --output develop/rbf-matmul.json

Choose sizes that fit the dense reference. Warmup and first-call compilation are separated from steady state. CUDA wall times are synchronized, path order is shuffled, and extra peak allocated bytes are measured above a live baseline. CPU tensor peak is not measured. Error paths record failure, not timing claims.

The eager direct PyTorch baseline may allocate N×M×D; compiled PyTorch can fuse that intermediate. This does not cover every optimized torch.cdist/norm-matmul formulation. Results include configuration, extension identity and environment metadata. Inspect exception paths before sharing logs.

The ML examples show k-NN regression, normalized RBF smoothing and attention with dense value and first-gradient checks. Benchmark --case kernel-regression or --case attention for end-to-end comparisons. Attention uses a dense matmul reference, without a broadcast feature intermediate; specialized PyTorch attention backends may already save score storage and run faster.

Read the benchmark methodology and harness source.

Complete Kernel Learning Workloads

examples/benchmark_ml.py compares complete ridge, landmark regression, PCA, LDA and rectangular SVD fits. Dense and streamed paths use the same inputs, initialization and numerical settings. Default dense-iterative stores the kernel and runs the same iterative method with direct ATen stored-matrix products; generated sources evaluate tiles. Its timing does not represent a direct dense factorization; use the optional direct baseline for that. Optional dense-direct uses a direct solve, augmented QR, full eigh or full SVD, labeled as a different numerical algorithm.

python examples/benchmark_ml.py --case ridge --device cuda --n 2048 \
    --dense-direct --output develop/ridge-memory.json
python examples/benchmark_ml.py --case landmark --device cuda --n 1024 --m 128 \
    --backward --dense-direct --output develop/landmark-backward.json

The timed scope includes source construction, fitting and held-out prediction; SVD fits leading factors. --backward adds a mean squared prediction objective and input VJPs for ridge/landmark. GPU memory is extra peak allocated bytes above live inputs/common auxiliary blocks. Validation/previous outputs are released before timing another variant; reserved cache and external CUDA allocations are excluded. CPU tensor peaks are null. JSON records samples, diagnostics, numerical errors, parameters, environment and harness/extension checksums.

Convergence and value/gradient or spectral-subspace checks must pass before measurement. A direct decomposition can be faster while using more memory; a small problem or a wide subspace can favor dense storage. Failures/OOMs are errors with nonzero exits, never performance results. See full methodology.

Measured Kernel Fit Memory

One CUDA measurement on a Quadro RTX 6000, PyTorch 2.12.1+cu126, float64. Three synchronized timing repeats after warmup; maximum extra peak tensor allocation above the pre-call live allocation baseline, with Python garbage collection before each sample. Warmed persistent workspaces are included in the baseline, so these figures are additional allocations, not total device usage. RBF gamma=1/8, feature width 8, chunk 128, query count 128, RHS width 2, regularization 0.5, rtol=1e-7, atol=1e-9. PCA/SVD retain rank 2 with search width 10; LDA uses four classes. All cases passed numerical checks and convergence before timing.

WorkloadLogical KernelDense MiBStreamed MiBDense SecondsStreamed Seconds
Ridge2048 x 204834.200.4510.46110.390
Landmark2048 x 2564.570.354.3613.947
PCA2048 x 204834.302.283.9422.939
LDA1024 x 10249.260.6016.01616.365
SVD2048 x 102418.292.282.2761.828
Ridge + backward1024 x 102440.230.995.0565.341
Landmark + backward1024 x 1285.110.932.4612.928

These recorded measurements predate the stored-matrix product optimization. They used the tiled matrix consumer; rerunning the current harness uses direct ATen products for stored matrices and can give different timings.

Here dense means a stored RBF matrix with the same native iterative solver, start, stopping rules and tiled matrix consumer as streaming. These timings isolate storing versus recomputing entries; they do not represent a direct dense BLAS fit. All fits except SVD include held-out prediction. Backward rows also include mean squared predictions and every floating input gradient.

For a conventional direct dense comparison on the same 2,048-sample ridge problem, the separate run measured:

Ridge VariantExtra Peak MiBMedian Seconds
Dense direct solve96.120.051
Stored matrix, matched iterative solver34.206.883
Streamed iterative solver0.457.468

The direct solver uses a different algorithm and is faster in this experiment; streaming saves matrix storage through repeated source evaluation. These are workload observations on a shared machine, with no general speed guarantee. CUDA-library/driver allocations and reserved cache are excluded; CPU peak is unmeasured. Narrower problems, wider search spaces or other hardware can change the balance. Raw samples, validation errors and checksums provide the exact configurations and provenance.