Skip to main content

FuncyFlows

Normalizing flows and flow matching on function spaces.

A function is represented by its coefficients on a Laplacian eigenbasis (CosineBasis or FourierBasis), the reference measure is a Gaussian on those coefficients, and a transport is a neural ODE in coefficient space. The vector fields (LinearField, MatrixField) have closed-form divergences, so the density of the transported measure relative to the reference measure is exact — no Hutchinson estimators — which makes reverse-KL training, likelihood training and importance reweighting usable at hundreds of modes.

pip install funcyflows            # torch + tqdm

Examples

python -m FuncyFlows.examples        # copies the example scripts into the current directory
python bimodal_posterior.py         # then run any of them
Script What it shows
bimodal_posterior.py 1D, y = f² + noise: pCN vs Laplace-GP vs reverse KL vs flow matching vs exact latent pCN
positivity.py 1D log-normal prior: positivity and skew that no Gaussian reproduces
nonstationary.py 1D random-lengthscale prior: a mixture of GPs, scored by roughness spread
ring_inpainting.py 2D rings, central hole: learned prior + latent pCN vs GP with the same variances
cloud_inpainting.py 2D saturated power-law fields, 256 scattered points
phase_inpainting.py 2D two-phase fields: the posterior marginal is two spikes, a GP's is a smear

The 2D examples and the two 1D prior examples use the spatial PointwiseField; the bimodal one is coefficient-space only. Each takes 3–12 minutes on a CPU. Except for cloud inpainting and phase inpainting which can take up to 50mins.

The pieces

Piece Role
CosineBasis(M), FourierBasis(M) M orthonormal basis functions on [0, 1]; basis.evaluate(points) → design matrix
GaussianReferenceMeasure(basis, alpha, power) or (basis, variances=...) base measure N(0, diag σ²) on the coefficients; .sample(n), .scale (= σ)
LinearField per-mode gain plus a data-dependent drift; exact for the linear-Gaussian part
MatrixField(conditioner) one wide tanh layer over all coefficients; global nonlinear mixing
SumField(*fields) add fields; traces add
ContinuousTransformation(measure, field, num_steps) the flow: .transport(v0), .push_forward, .pull_back, .log_rn_at(v)
FlowMatching, ConditionalFlowMatching, ReverseKL, NegativeLogL objectives; each is a callable returning a loss
train(objective, params, num_steps, learning_rate) Adam loop, returns the loss history
ImportanceCorrection, coverage_curve, latent_pcn diagnostics and an exact latent-space MCMC sampler

Every field takes mode_scale=measure.scale: the nonlinearity sees whitened coefficients and the divergence is unchanged (similarity transform). Always pass it.

1. Learn a prior from samples (flow matching)

import torch
from FuncyFlows.base_measures import CosineBasis, GaussianReferenceMeasure
from FuncyFlows.transports.continuous import (ContinuousTransformation, SumField, LinearField,
                                             MatrixField, TimeBasisConditioner)
from FuncyFlows.objectives import FlowMatching
from FuncyFlows.utils.train import train

M = 64
basis = CosineBasis(M)
coeffs = ...                                  # [N, M] training functions projected onto the basis
measure = GaussianReferenceMeasure(basis, variances=coeffs.var(0))   # diagonal Gaussian fit = the GP baseline

field = SumField(
    LinearField(M, num_time_modes=4),
    MatrixField(TimeBasisConditioner(M, 256, num_time_modes=4), mode_scale=measure.scale))
flow = ContinuousTransformation(measure, field, num_steps=16)

losses = train(FlowMatching(flow, coeffs, batch_size=256, weights=1 / measure.scale, coupling="optimal"),
               flow.parameters(), num_steps=10000, learning_rate=2e-3)

draws = flow.transport(measure.sample(1000))              # [1000, M] new functions
points = torch.linspace(0, 1, 400).reshape(-1, 1)
values = draws @ basis.evaluate(points).T                 # [1000, 400] on a grid
log_q = flow.log_rn_at(draws)                             # exact log density w.r.t. the reference measure

weights=1/measure.scale whitens the loss so high modes count; coupling="optimal" needs scipy and only makes sense for an unconditional flow.

2. Posterior from a likelihood (no samples needed)

from FuncyFlows.objectives import ReverseKL
from FuncyFlows.utils.gaussian_misfit import GaussianMisfit
from FuncyFlows.diagnostics import ImportanceCorrection

prior = GaussianReferenceMeasure(basis, alpha=0.05, power=2.0)     # Matérn-like spectrum
phi_obs = basis.evaluate(obs_points)                               # [n_obs, M]
forward = lambda v: (v @ phi_obs.T) ** 2                           # any differentiable map
potential = GaussianMisfit(forward, data, noise_std)               # -log likelihood, [batch] -> [batch]

flow = ContinuousTransformation(prior, field, num_steps=16)        # same field recipe as above
train(ReverseKL(flow, potential, num_samples=64, path_gradient=True), flow.parameters(), num_steps=3000)

draws = flow.transport(prior.sample(2000))
check = ImportanceCorrection(flow, draws, potential)               # exact because the traces are exact
print(check.efficiency, check.log_evidence)                        # efficiency near 1 = posterior matched

For an exact sampler on top of a learned prior use latent_pcn(flow, potential): pCN in the flow's latent space, whose acceptance needs no Jacobian.

3. Amortised posterior (condition on data)

from FuncyFlows.transports.continuous import DataConditioner
from FuncyFlows.objectives import ConditionalFlowMatching

C = ...                                                    # context dimension (e.g. whitened projected data)
field = SumField(
    LinearField(M, C, num_time_modes=4, mode_scale=measure.scale),          # data drift: the linear-Gaussian mean
    MatrixField(DataConditioner(M, 256, C, num_time_modes=4), mode_scale=measure.scale))
flow = ContinuousTransformation(measure, field, num_steps=16)

def simulate(batch):                                       # -> (target coeffs [batch, M], context [batch, C])
    ...

train(ConditionalFlowMatching(flow, simulate, measure.sample, batch_size=256, weights=1 / measure.scale),
      flow.parameters(), num_steps=20000, learning_rate=2e-3)

posterior = flow.transport(measure.sample(1000), context.expand(1000, -1))   # one ODE solve per posterior

Whiten the context by measure.scale when it is itself a projected function; otherwise the drift weights for high modes have to reach 1/σ and never do. When a conjugate (linear-Gaussian) posterior is available in closed form, use it as the base measure and let the flow learn the residual.

Diagnostics

from FuncyFlows.diagnostics import coverage_curve, coverage_error
levels, coverage = coverage_curve(samples, truths, weights=1 / measure.scale)   # TARP; samples [cases, draws, M]
coverage_error(levels, coverage)     # signed max gap from the diagonal: negative = overconfident

Layout

FuncyFlows/
  base_measures/     bases, GaussianReferenceMeasure
  transports/        continuous/: fields, conditioners, ContinuousTransformation
                     (grid_fields.py: PointwiseField, one FNO-style spatial layer with exact trace)
  examples/          python -m FuncyFlows.examples
  objectives/        flow_matching, reverse_kl, negative_logl
  samplers/          latent_pcn
  diagnostics/       importance, coverage
  utils/             train, gaussian_misfit

Metadata

Release files for funcyflows 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for funcyflows 0.2.1
File Size Uploaded
funcyflows-0.2.1.tar.gz 60.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for funcyflows 0.2.1
File Interpreter ABI Platform
funcyflows-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 135.8 kB

Release files / funcyflows-0.2.1.tar.gz

Download URL funcyflows-0.2.1.tar.gz
Size 60.4 kB
Tags Source
SHA-256 checksum
How to use checksums
c53cd4cde32e0843c73d52b2d6c7ff62969bb0ebe76d11fa1c1472e6e3f8f16d
BLAKE2b-256 checksum
How to use checksums
3c70ac3c0479858a72e635e67e676758a47891d1b90e20121637456305400e6f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / funcyflows-0.2.1-py3-none-any.whl

Download URL funcyflows-0.2.1-py3-none-any.whl
Size 75.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
656c055437d6fc15c30490ed54ecf07634326f13b07cc6e8a7c5e1f453c34300
BLAKE2b-256 checksum
How to use checksums
50f825db299405b313691953543fc248c6ff527520b2f8628c0bed9ae3124f83
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release history Release notifications | RSS feed

0.2.3

2 release files

0.2.2

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page