Skip to main content

OTP-FM: Multimarginal flow matching with optimal transport potentials

Project description

OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP) (ICML 2026)


PaperOverviewWhy OTP-FM?InstallationQuick startTutorialsDocumentationCitation


OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP)

Python 3.10+ CI coverage Code style: black Ruff pre-commit

A PyTorch library for training flow matching models with intermediate marginal constraints enforced using "optimal transport potentials". Includes code for reproducing Kansal et. al., ICML 2026.

Overview

OTP-FM extends vanilla conditional flow matching (CFM) between endpoint marginals to incorporate intermediate marginal constraints as well. We do so by modifying the dynamic optimal transport problem to incorporate potential energy terms corresponding to these intermediate marginals and updating the CFM targets based on the resulting dynamics.

Why OTP-FM?

  • Flexibility in the choice of potentials and temporal dynamics
  • Efficient training for a variety of potentials; in particular, linear time training with the $\mathcal W_2^\infty$ (W2Inf) potential
  • Stable training using the OTPFM curriculum
  • SOTA results in multimarginal inference tasks

Check out Quick Start and Tutorials to see it in action.

Installation

For Users (pip)

# Core package
pip install otpfm

# With W2Potential support (requires POT library)
pip install otpfm[w2]

To run experiments or develop (pixi)

Pixi is a fast conda-like package manager. Install it first:

curl -sSf https://pixi.sh/install.sh | bash

Then set up the environment:

git clone https://github.com/Bexorg-Inc/OTP-FM.git
cd otpfm
pixi install
pixi shell

Both the otpfm and experiments packages will be installed.

Quick Start

import torch
from collections import OrderedDict
from otpfm import OTPFM, Curriculum
from otpfm.potentials import W2InfPotential

# Training data: samples from each marginal (batch_size, num_marginals, dim)
# For K=2 intermediate times: [source, t=0.33, t=0.67, target]
xs = torch.randn(64, 4, 2)

# Define K = 2 intermediate marginal potentials
tks = [0.33, 0.67]  # Intermediate time points
potentials = OrderedDict({
    tks[0]: W2InfPotential(tk=tks[0], strength=100.0, lambda_type='gaussian', width=0.2),
    tks[1]: W2InfPotential(tk=tks[1], strength=100.0, lambda_type='gaussian', width=0.2),
})

# Create model
model = OTPFM(
    d=2,                          # Data dimension
    tks=tks,                      # Intermediate time points
    potentials=potentials,        # OT potentials
    flownet_args={
        'hidden_dim': 128,
        'num_hidden_layers': 2,
    }
)

optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
n_epochs = 100
iterations_per_epoch = 50

# This controls transition from vanilla flow matching (alpha=0) to full OTP-FM (alpha=1)
otp_alpha_schedule = Curriculum(total_iterations=n_epochs * iterations_per_epoch)  # Sigmoid schedule by default

iterations = 0
model.train()
for epoch in range(n_epochs):
    for batch_idx in range(iterations_per_epoch):
        otp_alpha = otp_alpha_schedule(iterations)

        # Forward pass
        loss = model.forward_with_loss(xs, otp_alpha=otp_alpha)

        # Backward pass
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # Update EMA model
        model.update_ema()
        iterations += 1

# Sample trajectories
model.eval()
x0 = torch.randn(100, 2)  # Initial samples
with torch.no_grad():
    trajectories, times = model.sample(x0, n_steps=10)

Tutorials

Customization

Potential Types

OTP-FM supports multiple potentials based on different statistical distances:

from otpfm.potentials import (
    W2InfPotential,          # Random coupling between samples; fastest and default recommendation
    W2Potential,             # Exact Wasserstein-2 (requires pot); usually better to use W2InfPotential with pre-computed OT couplings
    MMDRBFPotential,         # MMD with RBF kernel
    KLPotential,             # KL divergence with score estimation
)

Spatiotemporal dynamics

Spatial and temporal dynamics can be tuned by changing the strengths, widths, and shapes of the potentials, e.g.:

tks = [0.1, 0.5, 0.7]  # Intermediate time points
potentials = OrderedDict({
    tks[0]: W2InfPotential(tk=tks[0], strength=500.0, lambda_type='box', width=0.1),
    tks[1]: W2InfPotential(tk=tks[1], strength=400.0, lambda_type='triangle', width=0.2),
    tks[2]: W2InfPotential(tk=tks[2], strength=100.0, lambda_type='gaussian', width=0.05),
})

Custom Velocity Networks

You can provide your own velocity network:

import torch.nn as nn

class MyVelocityNet(nn.Module):
    def forward(self, x, t1, dt):
        """
        Args:
            x: (batch, d) positions
            t1: (batch,) start times
            dt: (batch,) time intervals
        Returns:
            v: (batch, d) velocities
        """
        # Your implementation
        pass

model = OTPFM(
    d=2,
    tks=[0.5],
    potentials=potentials,
    flownet=MyVelocityNet()  # Custom network
)

Citation

If you use this code in your research, please cite:

Coming soon.

Reproducing Experiments

For reproducing OTP-FM experiments from the ICML paper, see REPRODUCIBILITY.md. For details on our benchmarking of previous methods, see https://github.com/rkansal47/OTP-FM-benchmarking.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

otpfm-1.0.tar.gz (180.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

otpfm-1.0-py3-none-any.whl (136.6 kB view details)

Uploaded Python 3

File details

Details for the file otpfm-1.0.tar.gz.

File metadata

  • Download URL: otpfm-1.0.tar.gz
  • Upload date:
  • Size: 180.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for otpfm-1.0.tar.gz
Algorithm Hash digest
SHA256 a6a66c94dbc93bdc51e15215f475b705fd32c0195e7818321dedd6eb33aa83d8
MD5 8a03af727d7e95b999a675cc41045aff
BLAKE2b-256 7228fc28f8209a6f694a86aa5df2ec6d1430526c8508d0957319b9e0a81f9fa3

See more details on using hashes here.

Provenance

The following attestation bundles were made for otpfm-1.0.tar.gz:

Publisher: release.yml on Bexorg-Inc/OTP-FM

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file otpfm-1.0-py3-none-any.whl.

File metadata

  • Download URL: otpfm-1.0-py3-none-any.whl
  • Upload date:
  • Size: 136.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for otpfm-1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0f73bfe2eb221d00cfa47fc3cdf18d37e50bb37982168888af3bee61943c36f0
MD5 de05f99cb2d91a194b1edec4f2ddb812
BLAKE2b-256 1f07d2c9b05cf9b4a0a7a7b762f7fd56b90ad50b3b06e03c476e28767974653e

See more details on using hashes here.

Provenance

The following attestation bundles were made for otpfm-1.0-py3-none-any.whl:

Publisher: release.yml on Bexorg-Inc/OTP-FM

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page