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
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

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.0rc1.tar.gz (178.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.0rc1-py3-none-any.whl (134.8 kB view details)

Uploaded Python 3

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

Hashes for otpfm-1.0.0rc1.tar.gz
Algorithm Hash digest
SHA256 6579fd6d6cffe5c0c9814bbeb5d6af33f9e6051a75273e7c303132b1160e7dd0
MD5 31b79ae6083e6bf3132c9b54a06d4cc1
BLAKE2b-256 4f88e0bd0c06dfa2b5f60b55da5037278c63ba8479e897f0def8a016dc6cc349

See more details on using hashes here.

Provenance

The following attestation bundles were made for otpfm-1.0.0rc1.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.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

Hashes for otpfm-1.0.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 1d515b95cceac03594f03bd0f630a4c4e942160c3ec907cac0d6bf6c576c4041
MD5 ef94a601a2ea18f5bf02049a93e377d4
BLAKE2b-256 7980a0b5b6bf3fa15abccbe0940f67bb106dac5e5ade49a85a2c9e7ceb5829d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for otpfm-1.0.0rc1-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