Pre-trained models and datasets (pp.zoo, pp.datasets)¶
PINNeAPPle ships a small set of pre-trained surrogates and ready-made datasets. You can load each one in one line, then test, evaluate, validate, benchmark and fine-tune it.
Only items that passed a quality gate are included:
- Models: the held-out error against an independent reference is documented and reproducible, the physics checks of the trust report pass, and the validity range is recorded.
- Datasets: they were generated by a verified solver with convergence evidence, or come from a public source with a compatible licence; their schema, splits and provenance are documented.
The zoo catalogue lists every model and dataset with its metrics, size and Hugging Face link. The candidates that did not pass, and why, are listed in docs/dev/model_dataset_inventory.md.
| Model | What it predicts | Held-out error (reference) | Valid for |
|---|---|---|---|
airfoil_cst_rans_mlp |
Cl, Cd, Cm of a CST airfoil, with ensemble uncertainty | Cd 0.54 % and Cl ±0.003 on 15 unseen shapes (OpenFOAM RANS); Cd 2.8 % vs XFOIL | α −2…14°, Re 4·10⁶, CST bounds |
heatsink_forced_mlp |
Plate-fin heat-sink resistance (K/W), pressure drop (Pa), 95 % upper bound | R 0.71 %, ΔP 1.0 % on 900 designs (verified physics engine) | Ducted air, 0.5–6 m/s, 10–400 W |
heatsink_natural_mlp |
Plate-fin heat-sink resistance (K/W) in natural convection, 95 % upper bound; trained with monotonicity constraints | R 0.47 % mean, 1.5 % p95 on 750 designs (verified physics engine) | Free air, vertical fins, 1–80 W |
rover_contact_grc1_viper (v1.1.0) |
Drawbar pull, normal load, torque of a rover wheel on lunar regolith | Fx 0.22 N, Fz 0.47 N RMSE (ranges 1.4 and 1.4 kN), Wong–Reece with slip-sinkage | Slip −0.4…0.9, sinkage 2–60 mm |
| Dataset | Content |
|---|---|
airfoil_rans_cst_re4e6 |
436 OpenFOAM RANS runs, the grid study and an XFOIL polar |
heatsink_forced_convection, heatsink_natural_convection |
6000 and 5000 heat-sink designs solved by the physics engine |
rover_contact_grc1_viper |
Wong–Reece contact forces over slip × sinkage |
single_wheel_tests |
Published single-wheel testbed points (validation data) |
burgers_1d_li2021 |
1-D viscous Burgers, ν = 0.1: 1200 trajectories on 1024 points, t = 0…1 (Li et al. 2021 input distribution); Hub-only, 55 MB |
darcy_2d_li2021 |
2-D Darcy flow: 1200 piecewise-constant permeability / pressure pairs on 128 × 128 (Li et al. 2021); Hub-only, 71 MB |
navier_stokes_2d_li2021_nu1e-3 |
2-D Navier–Stokes vorticity, ν = 10⁻³: 1200 runs on 64 × 64, t = 0…50 (Li et al. 2021); Hub-only, 12 shards, 919 MB |
thermal_channel_cfd |
Actuated heated channel (2-D incompressible flow + heat, Re 100): 82 trajectories of u, v, p, T on 128 × 32 × 101 snapshots, with two unseen actuation families for testing; Hub-only, 8 shards, 360 MB |
airfoil_rans_cst_re4e6_fields |
The cell fields (p, Ux, Uy, nut on the 20064-cell O-grid) of the 436 OpenFOAM runs of airfoil_rans_cst_re4e6, same splits; Hub-only, 5 files, 186 MB |
cylinder_wake_lbm |
Confined cylinder wake (lattice Boltzmann, blockage 0.2): 720 vorticity snapshots of 9 saturated runs at Re 20–100, labelled by regime, Strouhal number and wake amplitude; Hub-only, 3 files, 64 MB |
Quickstart¶
The complete script is examples/model_zoo/quickstart.py.
1. Browse¶
import pinneapple as pp
pp.zoo.list() # ['airfoil_cst_rans_mlp', 'heatsink_forced_mlp', 'heatsink_natural_mlp', ...]
pp.datasets.list() # ['airfoil_rans_cst_re4e6', 'heatsink_forced_convection', ...]
card = pp.zoo.info("airfoil_cst_rans_mlp") # a ModelCard
card.task, card.inputs, card.outputs # names and units
card.validity_range["alpha_deg"] # [-2.0, 14.0]
card.validation_metrics["test/Cd_mape_pct"] # 0.545
print(card.to_markdown()) # the full card: training data, licence, citation, trust report
2. Load and predict¶
model = pp.zoo.load("airfoil_cst_rans_mlp") # weights checked against the manifest's SHA-256
naca2412 = dict(T0=0.169591, T1=0.144959, T2=0.131433, M0=0.024922, M1=0.071088, M2=0.058960)
out = model.predict(**naca2412, alpha_deg=[0, 2, 4, 6])
out["Cl"], out["Cd"], out["Cl_std"] # ensemble mean and spread
predict takes named columns (keyword arguments or a mapping) or an (n, n_inputs) array in model.input_names
order. It warns with pp.zoo.OutOfRangeWarning for rows outside the validity range. model.in_range(...) returns
the mask.
3. Compare with the reference¶
ev = pp.zoo.evaluate("airfoil_cst_rans_mlp")
ev["matches_card"] # True: the card's metrics are reproduced
ev["metrics"]["xfoil_naca2412/Cd_mape_pct"] # 2.8 -- against XFOIL, an independent solver
xfoil = pp.datasets.load("airfoil_rans_cst_re4e6", split="xfoil_naca2412")
pred = model.predict({k: xfoil[k] for k in model.input_names})
evaluate(name, dataset) also accepts any of the following, and reports the share of cases outside the validity
range:
- a dataset name with
split=; - a
DatasetSplit; - an
(inputs, outputs)pair; - a mapping of columns.
4. Validate¶
report = pp.zoo.validate("airfoil_cst_rans_mlp", inputs={**naca2412, "alpha_deg": [4.0, 18.0]})
report.decisao # "REVIEW": alpha = 18 deg is outside the validity range (and a warning is raised)
print(report.to_markdown()) # card metrics reproduced, acceptance limits, physics checks
The physics checks are model-specific:
- Airfoil: lift slope against thin-airfoil theory, Cl monotone below stall, symmetric-airfoil antisymmetry, Cd > 0.
- Heat sink: monotonic effects of airflow, conductivity and source size; positivity.
- Rover:
- load grows with sinkage;
- pull grows with slip before its peak;
- the Mohr–Coulomb limit holds;
- My ≥ R·Fx.
5. Benchmark¶
res = pp.zoo.benchmark("airfoil_cst_rans_mlp", budget_s=30, seeds=3)
res.table(); print(res.to_markdown())
This runs pp.benchmark (the equal-budget benchmark API) on the model's own dataset. It compares the shipped model with
the same architecture trained from scratch on the same training split under the same compute budget, and evaluates
both on the same held-out test set. The run includes pp.benchmark's gates, determinism check, report and trust card.
It needs pinneapple_analysis.benchmark.
6. Fine-tune, with a held-out guard¶
new = pp.datasets.load("airfoil_rans_cst_re4e6", split="naca_reference") # or (X_new, Y_new), or a dict of columns
ft = pp.zoo.finetune("airfoil_cst_rans_mlp", new, epochs=50, lr=1e-5)
print(ft.summary()) # guard metrics on the ORIGINAL test set, before -> after
ft.save("my_airfoil") # refused (FinetuneRejectedError) if any got worse by > 2 %, unless force=True
pp.zoo.load_dir("my_airfoil")
How the fine-tune works:
- It trains a copy of the model; the original is never modified.
- By default (
replay=True) the original training split is mixed in, to limit forgetting. NaN targets (e.g. a missing Cm) are ignored. save_to=/registry=(apinneapple_registry.ArtifactRegistry) save in the same call, and are refused the same way.- A forced save records the regression in the new card's
data_lineage["guard"].
The guard is not a formality. Fine-tuning this airfoil model on 36 extra NACA runs makes it worse on the 15 held-out shapes, and the guard refuses to save it.
7. Check each query, export to ONNX, run in a browser¶
validate judges a model as a whole. query_checks judges one input. It answers four questions:
- Is the input inside the validity range?
- Are the outputs physical?
- Does the ensemble agree as well as it did on held-out data?
- Do the model's physics checks hold at this point?
Each check carries the rule ID that pinneapple check uses: PC601 (out of range), PC602 (sparse region), PC402 and
PC403 (unphysical or non-finite output), and PC302 (model residual). The verdict is the worst status among them:
fail: the query is outside the range, so the answer is an extrapolation.warn: use the answer with caution.pass: every check holds.
Design constraints, such as the airfoil's t/c ≥ 11 % and |Cm| ≤ 0.10, are reported but never change the verdict.
r = pp.zoo.query_checks("airfoil_cst_rans_mlp", T0=.17, T1=.15, T2=.13, M0=.02, M1=.07, M2=.06, alpha_deg=16)[0]
r["verdict"]["status"] # 'fail': alpha 16 deg is beyond the trained range
[(c["id"], c["status"], c["detail_text"]) for c in r["checks"] if c["status"] != "pass"]
pp.zoo.check_spec("airfoil_cst_rans_mlp") # the declarative spec: values, limits, templates, verdicts
export_onnx writes one ONNX graph that takes the card's physical inputs and returns its physical outputs. The
graph includes the pre-processing (CST encoding, log features, normalisation) and the post-processing (exp,
ensemble mean and spread, conformal bound).
The export is refused if the graph differs from the PyTorch model by more than 1e-5. The comparison runs on every
held-out and independent-reference input of the zoo. With checks=True, the graph also takes a query input and
returns the values of the query checks, so any ONNX runtime gives the same verdict as Python.
info = pp.zoo.export_onnx("heatsink_forced_mlp", "heatsink.onnx", checks=True)
info["parity"] # 1.6e-06, against PyTorch on the 900 held-out designs
pp.zoo.onnx_parity("heatsink_forced_mlp", "heatsink.onnx")
For the browser, python -m pinneapple_hub.zoo.web data/web/zoo writes each model's graph and a JSON file. The JSON
holds the model card, the check spec and the reference data to plot. The same command writes the CST airfoil
geometry graphs from pinneapple_design.aero.geometry_onnx.
The JS runtime that reads these files ships with the package in pinneapple_hub/zoo/web/:
onnx.js, a dependency-free ONNX interpreter;zoo.js, withloadModel,loadAirfoilGeometryandevaluate.evaluateis the JS twin ofpinneapple_hub.zoo.checks.evaluate.
tests/js/zoo_web.test.mjs checks the runtime against Python. The Playground is built on it.
Datasets¶
info = pp.datasets.info("heatsink_forced_convection")
print(info.to_markdown()) # splits, columns with units, provenance, verification, licence
test = pp.datasets.load("heatsink_forced_convection", split="test")
test["r_base_k_w"], test.columns, test.n_rows
X, Y = test.xy(["base_width_mm", "power_w"], ["r_base_k_w"])
test.to_pandas()
The zoo datasets are also registered in pinneapple_data.datasets.DatasetRegistry as zoo/<name>.
Operator-learning benchmarks¶
burgers_1d_li2021, darcy_2d_li2021 and navier_stokes_2d_li2021_nu1e-3 are the standard problems of Li et al. (2021, the Fourier Neural Operator
paper), with the input distributions of that paper. They are generated by PINNeAPPle's own verified solvers
(pinneapple_simulation.numerical_solvers.operator_datasets). Each dataset card gives:
- the resolution and time-step study (discretisation error 4e-12 for Burgers and about 8e-4 for Darcy, in relative L2);
- the solver checks: the Cole–Hopf exact solution for Burgers, a manufactured solution with second-order convergence for Darcy;
- the seeds and the 1000 / 200 train / test split.
train = pp.datasets.load("darcy_2d_li2021", split="train") # downloads ~59 MB once
a, u = train["a"], train["u"] # (1000, 128, 128) each
Airfoil flow fields¶
airfoil_rans_cst_re4e6_fields holds the cell fields of the same 436 OpenFOAM runs as airfoil_rans_cst_re4e6,
regenerated with pinneapple_design.aero.dataset (the coefficients match the campaign; only runs flagged
unsteady differ, within their oscillation). Each row is one run: CST weights, angle of attack, coefficients,
status, and the cell centres and p, Ux, Uy, nut at the 20064 cells (free stream U = 1, chord 1). The grid moves with
the shape, so x and y are stored per run. The test split holds the 15 shapes held out by the airfoil surrogates.
test = pp.datasets.load("airfoil_rans_cst_re4e6_fields", split="test") # one 26 MB file
steady = test["status"] != "unsteady"
p, x, y = test["p"][steady], test["x"][steady], test["y"][steady] # (n, 20064) each
Heated-channel digital twin¶
thermal_channel_cfd is the actuated heated-channel problem of PINNeAPPle-Benchmark experiment 05, generated by the
library's 2-D incompressible solver (pinneapple_simulation.numerical_solvers.thermal_channel_datasets). The inlet
velocity U(t) and the heater flux Q(t) vary in time; each trajectory stores the fields, the actuators and the
quantities of interest (outlet temperature, maximum temperature, pressure drop, heat balance). The card gives the
solver checks (Poiseuille, Nusselt number 5.388 vs 5.385), the three-grid GCI of the dataset grid (1–4 %) and the
split plan: 48 / 8 / 12 trajectories of random steps and ramps, plus 8 chirped-sinusoid and 6 edge-step trajectories
from actuation families never seen in training.
test = pp.datasets.load("thermal_channel_cfd", split="test_unseen_sinusoid") # one 37 MB file
fields = test["fields"] # (8, 101, 4, 128, 32): u, v, p, T every 0.4 time units
Hosting, cache and offline use¶
Every file is listed in pinneapple_hub/zoo/manifest.json with its SHA-256, size, version and its Hugging Face Hub
location (huggingface.co/PINNeAPPle-Labs/<name>, tag revision: v<version>, or v<version>.postN when the same
files were republished with a new card or derived formats; the loader then falls back to v<version> while the new
tag does not exist yet, and checks the SHA-256 either way). A file is resolved in this order:
- Bundled with the package. This applies only to small items (under about 2 MB each). Larger datasets are
Hub-only: one
<split>.npzper split, possibly sharded into<split>-NN.npz, andpp.datasets.load(name, split=...)downloads only the files of the requested split. - The cache.
$PINNEAPPLE_ZOO_CACHE, by default~/.cache/pinneapple/zoo. - The Hub. The file is downloaded once with
huggingface_hub(or plain HTTPS), checked and cached.
Each step can fail with a specific error:
- A checksum that does not match raises
pp.zoo.ChecksumMismatchError. A corrupt cache entry is fetched again when online. - With
PINNEAPPLE_OFFLINE=1(orHF_HUB_OFFLINE=1), nothing is downloaded, and a file that is not available locally raisesArtifactUnavailableError. - An item the Hub does not have yet raises
ArtifactNotPublishedError, which says how to publish it.
pp.zoo.status(name) and pp.datasets.status(name) show where each file resolves from, without downloading it.
Versions and the physics a model was trained on¶
A new release of a model keeps the old one: the manifest lists it under superseded, with its files, their
SHA-256, its Hub tag and the reason it was superseded. pp.zoo.versions(name) lists every release, and
pp.zoo.load(name, version="1.0.0") loads an old one (from the Hub, with a SupersededModelWarning that gives the
reason).
A surrogate is only a surrogate of the physics it was trained on. The rover contact model records that physics in
its card (data_lineage.physics) and its config.json: the full WongReeceModel.physics_config(), i.e. the soil
and all its parameters, the wheel, gravity and the model options. ZooContactSurrogate(pp.zoo.load(...))
.check_physics(model) lists what differs from the model it is used with and raises a PhysicsMismatchWarning;
vehicle.dashboard.vehicle_dashboard does this and notes any difference on the PINN chart. A test fails when the
library's GRC-1, the VIPER wheel or the Wong-Reece options change without a new zoo release.
rover_contact_grc1_viper v1.0.0 was trained before slip-sinkage was added to GRC-1 (n1 = 0). Against the current
Wong-Reece model it errs by up to ~200 N at high driving slip. v1.1.0 is trained on the current physics (n1 = 0.8)
with the same held-out protocol (2000 Latin-hypercube states, seed 1000, disjoint from training). With slip-sinkage
the pull need not grow with slip at a fixed sinkage, so its gate checks that dFx/ds has the sign of the recorded
reference instead of dFx/ds ≥ 0.
On the Hugging Face Hub¶
What each repo holds besides the manifest's files, so the Hub's own tools work:
- Datasets: Parquet for the dataset viewer and
datasets.load_dataset. The npz files stay the source of truth (the library reads them, SHA-256 checked).pinneapple_hub.zoo.parquet.write_datasetderives Parquet files from them, deterministically, underdata/<config>/<split>-NNNNN.parquet. Scalar columns are plain columns; array columns are fixed-size list columns of their shape (List(List(float32, length=128), length=128); usewith_format("numpy")). A sample larger than 512 KiB whose arrays share a leading axis (Navier-Stokes vorticity in time, the heated-channel fields in time) is stored as one row per step, withsampleandstepcolumns, so the viewer can show every row. Shards stay below 256 MiB uncompressed and row groups below 16 MiB. Splits with other columns (a grid, a convergence study, an XFOIL polar) are separate configs. The README's YAML (configswith thedata_filesof each split,dataset_infofeatures and split sizes, licence, task category, size category, tags) is written bypinneapple_hub.zoo.hub_cards.dataset_card, with the columns, units, provenance and both ways of loading.pinneapple_hub.zoo.parquet.read_parquetputs a split back together; a test checks the round trip npz -> Parquet -> arrays bit for bit. - Models: safetensors, ONNX and an honest card. Next to
weights.ptthe repo holdsmodel.safetensors(the same tensors;pinneapple_hub.zoo.export.load_safetensors),model.onnx(pre- and post-processing in the graph, parity with PyTorch within 1e-5) andexample.json(a held-out case, the model's outputs for it and the verdicts). The card sayslibrary_name: pinneapple,pipeline_tag: tabular-regressionandinference: false: no Hub inference provider can run these models with their encoding, ensembles and validity checks, so the widget is off. Its "How to use" code prints the numbers ofexample.json, and the publishing workflow runs it. - The demo Space
PINNeAPPle-Labs/zoo-demo, linked from every model card, replaces the widget. It is a static Space (no server, free for the organisation):pinneapple_hub.zoo.demo.export_spacecomputes, from the models published on the Hub, every model's prediction along a sweep of each input with its ensemble spread or conformal bound and the verdict of its query checks at every point, and writes them with each model's ONNX graph (prediction and query checks in one graph) and the browser runtime (pinneapple_hub/zoo/web). The page (apps/zoo_space, UI only, with the Playground's charts) plots a sweep, shades where the checks warn (yellow) or fail (red), and shows the checks at the chosen input and the model's trust decision. Evaluate in your browser runs the graph on the visitor's own inputs.tests/js/zoo_space.test.mjsruns every precomputed sweep again through the browser runtime and requires the same numbers and the same verdicts as Python.
To publish (maintainers):
- Add a repository secret
HF_TOKENwith write access to the PINNeAPPle-Labs organisation. - Run the Publish zoo to the Hugging Face Hub workflow. It also runs on every push to
mainthat changes the zoo.
Hub-only items are not in the repository. To upload them, put their files on a branch as <name>/<file> (for
example the output of scripts/zoo/operator_data.py generate) and run the workflow by hand with staging_ref set to
that branch. Every staged file must match the SHA-256 in the manifest. Runs without staging_ref convert the
Hub-only datasets from the copy already on the Hub (downloaded, SHA-256 checked) and upload only their Parquet files
and card.
After the upload the workflow checks, anonymously, what the Hub serves, and fails when something is wrong:
- every manifest file at the tag (SHA-256);
- the dataset viewer of every dataset: datasets-server
is-valid(preview and viewer, retried while the Hub processes the push),splits(every split of the manifest is listed) andfirst-rowsof every split; - every model: loaded from the Hub with the bundled copy ignored, its card metrics reproduced on the stored reference, the card's example reproduced by the PyTorch, safetensors and ONNX copies, the card's front matter, and its "How to use" code run;
- the Space reaches
RUNNINGand serves its page,index.jsonand every model's sweeps.
To republish files that did not change (a new card, new derived formats), bump the item's revision to the next
v<version>.postN: the old tags stay as they are, so code pinned to them keeps working. Inputs of the manual run:
only (some items), tags=false (upload to main without creating the tags, e.g. to try a branch; the model
checks then wait for the tagged run) and space.
Without the secret, the workflow checks the manifest and skips the upload with a warning.
python scripts/zoo/publish_to_hub.py --dry-run builds the derived files and cards and lists the uploads;
--verify-viewer, --verify-models and --verify-space run the checks from any machine that reaches the Hub.