Parametrize the FNO spectral weight tensor as a sum of R rank-(L,L,1) block-terms - the sum-of-Tucker-blocks that CP-FNO and Tucker-FNO bracket but neither covers - and route a mixture of them (MoBTE) so one foundation operator serves many distinct PDEs. On real multi-PDE PDEBench at matched parameters the block-term kernel beats CP ~8-14%, and MoBTE adds a margin that grows monotonically with spectral diversity - a pure-generalization win (the train error is identical, the test-train gap shrinks). The algorithm is in core/: the kernel in bt_kernel.py (BlockTermSpectralConv), the mixture in moe_bt.py (MoBTESpectralConv). This maps code to results to claim.
core/ common.py (build/train/eval/real_dof, device select), bt_kernel.py (BlockTermSpectralConv),
moe_bt.py (MoBTE), analyze.py (gate-printing CLI), paths.py (DATA/RESULTS anchors)
data_gen/ gen_multipde.py, gen_pdebench_multi.py, gen_rd.py, pdebench_{to,cfd_to}_pt.py (converters)
campaigns/ the keeper campaigns; each writes to results/<campaign>/
hpc/ SLURM jobs + the login-node venv build (build_login.sh)
results/ generated result JSONs/figures (regenerable; not in the deposit)
data/ .pt caches (rebuilt from the recipes below; not in the deposit)
paper.py local artifacts (mechanism + Darcy sanity) + the HPC campaign map
Paths resolve through core/paths.py (no CWD-relative and no user-specific paths). $BTFNO_DATA (default data/), $BTFNO_RESULTS (default results/), $BTFNO_MODELS (default models/) and $BTFNO_PDEBENCH (default data/pdebench/, where you downloaded the raw PDEBench HDF5) override the anchors; the SLURM jobs set them to the cluster layout.
This is an HPC project. The headline TABLES are GPU SLURM campaigns that need the PDEBench .pt caches built on the cluster login node. Three pieces run on a normal machine: the mechanism (frequency-smoothness) analysis from the committed checkpoint, the Darcy sanity frontier, and the significance tests (campaigns/gen_gap_theory/run_stats.py, stdlib only, reads the committed result JSONs). paper.py runs those three; the cluster runs the rest.
The reported campaigns ran one GPU per job on a Slurm cluster: NVIDIA A40 (the five-family headline, the diversity-scaling sweep, and the generalization-gap runs), NVIDIA L40, NVIDIA RTX 6000, and one A100 node for the mechanism analysis; hosts are 64-core Intel or AMD with 120-257 GB RAM, Ubuntu 22.04.5. Software: Python 3.10, PyTorch 2.12.0 (CUDA 13.0, cuDNN 9.20), neuraloperator 2.0.0, tensorly 0.9.0, tensorly-torch 0.5.0, numpy 2.2.6.
| campaign | runner | output | claim |
|---|---|---|---|
darcy_sanity |
run_darcy.py, smoke_test.py
|
results/darcy_sanity/ |
single-physics sanity: block-term competitive vs CP/Tucker/dense |
mobte_multipde |
run_pdebench_multi.py (the gate + expert-kind ablation), run_multipde_full.py (synthetic precursor) |
results/mobte_multipde/ |
MoBTE-SM beats BT +17% / CP +29% at 5 real families; block-term experts beat CP experts (~9%); the block-term kernel is an 8-14% floor over CP |
diversity_scaling |
run_pdebench_scaling.py |
results/diversity_scaling/results_pdebench_scaling.json |
the diversity-scaling LAW: MoBTE-vs-BT margin monotone in #families (N=2 tie to N=5 +15%), driven by spectral bandwidth diversity (bimodal at N=3) |
gen_gap_theory |
run_pdebench_theory.py, run_stats.py (significance) |
results/gen_gap_theory/results_pdebench_theory*.json, stats_tests.json
|
pure generalization: gen-gap CP 0.029 to BT 0.019 to MoBTE 0.012 at matched fit; block-term vs CP experts significant on five families ( |
mechanism |
block_analysis.py, save_ckpt.py
|
results/mechanism/block_analysis.png |
block-term spectral kernel smoothest across frequency (freq-TV 0.715 < 0.797 < 0.924), so discretization-robust (the why) |
core/analyze.py reads any results_*.json frontier and prints the matched-DOF gate.
pip install -r requirements.txt # torch numpy matplotlib neuraloperator tensorlyLocal (no GPU needed), from this directory:
PYTHONPATH=. python paper.py # mechanism + Darcy sanity + significance tests -> results/
PYTHONPATH=. python campaigns/darcy_sanity/smoke_test.py # build/backprop sanity, no data
PYTHONPATH=. python core/analyze.py results/mobte_multipde/results_pdebench_multi_5fam.jsonDevice auto-selects cuda > mps > cpu; MPS has no float64 and OOMs on the big multi-branch models, so the campaigns go to the cluster.
HPC (the cluster home mirrors this package tree):
# push code (no data/caches):
rsync -az --exclude 'data/' --exclude '__pycache__' --exclude '*.pt' . utwente:~/projects/btfno/
# build the env once on the login node: bash -l hpc/build_login.sh
# build the PDEBench caches on the LOGIN node (the HDF5 live on /local), ship the small .pt:
cd ~/projects/btfno && PYTHONPATH=. BTFNO_DATA=~/projects/btfno/data \
BTFNO_PDEBENCH=/path/to/raw/pdebench python \
data_gen/gen_pdebench_multi.py --M 900 --N 64 --out data/pdebench_multi.pt
# launch (the slurm jobs set PYTHONPATH / BTFNO_DATA / BTFNO_RESULTS and write results/<campaign>/):
sbatch hpc/job_pdebench_multi.slurm # the multi-PDE gate
sbatch hpc/job_pdebench_scaling.slurm # the diversity-scaling law
sbatch hpc/job_pdebench_theory.slurm # the gen-gap decompositionAll datasets free / public; raw archives + the large .pt caches stay out of the repo (rebuilt from the recipes). PDEBench (Takamoto et al., NeurIPS 2022 D&B; open DaRUS / GitHub) supplies the real families (2D_diff-react.h5, 2D_CFD_M0.1_128.h5, 2D_rdb_NA_NA.h5, 2D_CFD_M1.0_*.h5); data_gen/gen_pdebench_multi.py builds the multi-family .pt on the login node. The neuraloperator package auto-downloads Darcy / Navier-Stokes. The synthetic 6-PDE corpus is generated in-house by data_gen/gen_multipde.py (pseudo-spectral solvers, reproducible from seeds).
Matched real-DOF comparisons only (core.common.real_dof, complex = 2 reals), per-task error + multi-seed sd. The block-term kernel is a drop-in for neuralop's SpectralConv weight: same forward, mode-truncation, optimizer, and seed handling, only the spectral parametrization varies, so baselines are the shipped neuraloperator TFNO/CP/dense at matched budget.
License: MIT (LICENSE).