OTP-FM: Multimarginal flow matching with optimal transport potentials
Project description
OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP) (ICML 2026)
Paper • Overview • Why OTP-FM? • Installation • Quick start • Tutorials • Documentation • Citation
OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP)
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
for epoch in range(n_epochs):
for batch_idx in range(steps_per_epoch):
model.train()
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
- 01_quickstart_gaussians.ipynb: Quickstart on synthetic gaussian data.
- 02_singlecell_eb.ipynb: Embryoid body scRNA-seq data.
- 03_gulf_of_mexico.ipynb: Modeling ocean currents in the Gulf of Mexico.
- 04_beijing_airquality.ipynb: Beijing air quality data.
- 05_exact_gaussian_solutions.ipynb: Exact solutions for dynamic OT with potentials for Gaussian marginals.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file otpfm-1.0.0rc1.tar.gz.
File metadata
- Download URL: otpfm-1.0.0rc1.tar.gz
- Upload date:
- Size: 178.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6579fd6d6cffe5c0c9814bbeb5d6af33f9e6051a75273e7c303132b1160e7dd0
|
|
| MD5 |
31b79ae6083e6bf3132c9b54a06d4cc1
|
|
| BLAKE2b-256 |
4f88e0bd0c06dfa2b5f60b55da5037278c63ba8479e897f0def8a016dc6cc349
|
Provenance
The following attestation bundles were made for otpfm-1.0.0rc1.tar.gz:
Publisher:
release.yml on Bexorg-Inc/OTP-FM
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
otpfm-1.0.0rc1.tar.gz -
Subject digest:
6579fd6d6cffe5c0c9814bbeb5d6af33f9e6051a75273e7c303132b1160e7dd0 - Sigstore transparency entry: 1704892427
- Sigstore integration time:
-
Permalink:
Bexorg-Inc/OTP-FM@d68e3d2dbb99d84632d86e8792f99bbf16909b54 -
Branch / Tag:
refs/tags/v1.0.0rc1 - Owner: https://github.com/Bexorg-Inc
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d68e3d2dbb99d84632d86e8792f99bbf16909b54 -
Trigger Event:
release
-
Statement type:
File details
Details for the file otpfm-1.0.0rc1-py3-none-any.whl.
File metadata
- Download URL: otpfm-1.0.0rc1-py3-none-any.whl
- Upload date:
- Size: 134.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d515b95cceac03594f03bd0f630a4c4e942160c3ec907cac0d6bf6c576c4041
|
|
| MD5 |
ef94a601a2ea18f5bf02049a93e377d4
|
|
| BLAKE2b-256 |
7980a0b5b6bf3fa15abccbe0940f67bb106dac5e5ade49a85a2c9e7ceb5829d7
|
Provenance
The following attestation bundles were made for otpfm-1.0.0rc1-py3-none-any.whl:
Publisher:
release.yml on Bexorg-Inc/OTP-FM
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
otpfm-1.0.0rc1-py3-none-any.whl -
Subject digest:
1d515b95cceac03594f03bd0f630a4c4e942160c3ec907cac0d6bf6c576c4041 - Sigstore transparency entry: 1704892455
- Sigstore integration time:
-
Permalink:
Bexorg-Inc/OTP-FM@d68e3d2dbb99d84632d86e8792f99bbf16909b54 -
Branch / Tag:
refs/tags/v1.0.0rc1 - Owner: https://github.com/Bexorg-Inc
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d68e3d2dbb99d84632d86e8792f99bbf16909b54 -
Trigger Event:
release
-
Statement type: