Skip to content

Distributions

SDOT handles a wide variety of distributions. To handle the large number of combination, the library generates and compiles the appropriate C++ kernels at runtime.


Source and target can be either a discrete measure (a sum of Dirac masses) or a a continuous density.

All the axes are named. The axes sizes can be dynamic and depend of other axis variables.

For instance,

python
@aggregate
class Image( Distribution ):
    values      : Tensor( "shape( dim )" )
    frame       : Tensor( "dim + 1", "dim" )
    knots       : Tensor( "nb_knots[ dim ]" )

img = Image( values = [ [ 1, 2 ] ], knots = [ [ 1, 5 ], [ 6 ] ] )
print( img.dim )   # -> 2
print( img.shape ) # -> [ 1, 2 ]
print( img.nb_knots ) # -> [ 2, 1 ]

Variants and aggregate

All the distributions (as well as all the classes with intermediate quantities like Cell, PowerDiagram, ...) have variants

  • BatchOf prepends a batch_size axis on each Tensor and DynamicSize.
  • 1d specifically sets dim axes variables and remove the axes that reduce to value 1.

Typically, BatchOf is useful for GPU-parallel workloads. Besides, it can be used to get a list of objects in a compact way. For instance PowerDiagram.cells actually return a BatchOfCell.

Here is an example of BatchOf usage:

python
# Compute 64 distances in parallel
f = sdot.BatchOfSumOfDiracs( positions ) # shape (64, n, d)
g = sdot.BatchOfSplineGrid( values )     # shape (64, ...)

d = sdot.distances( f, g )               # shape (64,)

1d is useful to simplify the codes, because of the lower ranks tensors. Here is an example :

python
from sdot import BatchOfSumOfDiracs, BatchOfSplineGrid, distance

# Compute 64 distances in parallel
f = sdot.PiecewiseAffine1d( [ 0, 1 ], knots = [ 2, 3 ] ) # knots is a single tensor
g = sdot.SumOfDiracs1d( [ 1, 2, 3 ] ) # position : rank 1 tensor. Equivalent to SumOfDiracs( [[1],[2],[3]] )
f = SumOfDiracs1d( [ 0.1, 0.5, 0.9 ] ) # equivalent SumOfDiracs( [ [0.1], [0.5], [0.9] ] )

d = sdot.barycenters( f, g ) # shape (3,) (and not (3,1))

Masses

By default, all the distributions are normalized so that the mass is 1 (excepted when integrals are not finite)

  • if the user specifies mass = None, nothing is done, the distribution is used as-is
  • it is possible to fix a specific value, like mass = 0.5

If the source and target have different total masses, SDOT automatically performs partial transport — only the overlapping mass fraction is transported (see the partial tag in the Examples gallery →)

Discrete distributions

ClassDescription
SumOfDiracs( positions )Equal-weight Dirac masses at given positions
SumOfWeightedDiracs( positions, weights )Dirac masses with explicit weights
python
# 500 uniform diracs in 2D
f = sdot.SumOfDiracs( np.random.rand( 500, 2 ) )

# 50 diracs with explicit masses (must sum to 1 for full transport)
positions = np.random.rand( 50, 2 )
weights = np.ones( 50 ) / 50
f2 = sdot.SumOfWeightedDiracs( positions, weights )

Continuous distributions

ClassDescription
SplineGrid( values, continuity=1 )C0, C1 or C2 spline density on a regular grid
PolynomialGrid( values )Piecewise polynomial density on a regular grid
Grid( values )Shortcut for PolynomialGrid with 1 coefficient
SumOfGaussians( centers, covariances, weights )Mixture of Gaussians (coming soon)
PolynomialCells( ... )Sum of polynomials defined on a batch of cells (coming soon)
Cells( ... )Shortcut for PolynomialCells with 1 coefficient per polynomial (coming soon)
Mesh( vertices, values, elements )Density on a mesh, following the vtk conventions (coming soon)

PolynomialGrid

Piecewise polynomial density on a regular axis-aligned grid. Efficient for images and simple analytic densities.

python
from sdot import PolynomialGrid
import numpy as np

# Uniform density on [0,1]²  (1×1 cell, constant value 1)
uniform = PolynomialGrid( values = [[[1]]] )

# From a grayscale image
img = np.load( "cat.npy" ).astype( float )
g   = PolynomialGrid( values = img )
ArgumentTypeDescription
valuesarray (n₁, …, nₐ)Cell values — one scalar per cell (Q₀ basis)
domain[[min…], [max…]]Bounding box (default: [0,1]ᵈ)
massfloatTotal mass (default: 1 — normalized automatically)

SplineGrid

C¹ or higher continuity spline density on a grid. Can be seen as a specialization of PolynomialGrid.

python
from sdot import SplineGrid
import numpy as np

# 1D natural cubic spline from sampled values
s = SplineGrid( [ 1, 4, 9, 4, 1 ], continuity = 1 )

# 2D spline from random values
g = SplineGrid( np.random.rand( 10, 10 ), continuity = 1 )
ArgumentTypeDescription
valuesarraySpline control values on the grid
continuityintSmoothness order (1 = C¹, 2 = C², …)
domain[[min…], [max…]]Bounding box (default: [0,1]ᵈ)
massfloatTotal mass (default: 1)

SumOfGaussians (coming soon)

Coming soon

Mixture of Gaussians target. Enables analytic integrals over Laguerre cells and is particularly useful for learning problems.

python
from sdot import SumOfGaussians

g = SumOfGaussians(
    centers     = centers,      # (k, d)
    covariances = covs,         # (k, d, d)
    weights     = weights,      # (k,)
)

Mesh (coming soon)

Coming soon

Density defined on a surface or volume mesh (triangles, tetrahedra, …). Supports non-uniform density fields on unstructured grids.

PolynomialCells (coming soon)

Coming soon

Piecewise polynomial density on a polyhedral cell decomposition — the most general target type.

SDOT — H. Leclerc, Q. Mérigot, T. Gallouët · LMO / INRIA PARMA