YMF is a plain-YAML record of a computational physics problem: what it models and why, the equations, how they were discretized and solved, and what came out. It is meant to be written and reviewed by people and LLMs together, and read and written by solvers.
It has two parts that share one format:
| Problem specification | Archive | |
|---|---|---|
| What | The problem from physics down to solver settings: processes and assumptions, strong form, weak forms, discretizations, verification plan | A solver's output: which mesh, time and field each HDF5 dataset holds |
| Written by | a person, an LLM, or both | the solver |
| Checked by | ymf.schema (strictyaml) and ymf.units (pint): schema, units, dimensional consistency |
ymf.archive: structure and array shapes only, cheaply |
| Depends on | ymf[frontend] |
pyyaml alone |
| Guide | docs/problem-specification.md | docs/archive-format.md |
The two meet in the archive of a spec: the specification as realized,
followed by every output computed from it, each keyed by a hash of the part
of the spec that produced it and recording how it was produced. Such an
archive is itself a spec: rerunning it reproduces its outputs bitwise, and
another spec can extends it.
Between "simulate this system" and "run this solver" sit a dozen decisions:
which physics to keep, which unknowns, which weak form, which elements,
which stabilization. They usually live in someone's head, a notebook, or a
solver input file that records the outcome but not the reasoning. YMF makes
each stage a structured, reviewable block, and tags each one with its
provenance: whether an LLM proposed it, a person wrote it, or a person
edited an LLM's proposal. One document can hold several branches
(competing weak forms and discretizations of the same problem) alongside
the plan for verifying them against each other. Branches can also be
separate files: an abstract document holding the model (the physics
and equations, often most of the work for multiphysics) is written once and
extended, file by file, into many well-posed specs. A document records how
far it has got -- ymf_state: abstract, well-posed, discretized, closed --
and the loader checks that its content gets that far.
On the output side, solver archives have usually been XDMF XML written by hand-built element trees, with no data model behind them and nothing checking that the metadata matches the arrays. The YMF archive is a small, explicit data model that converts one-to-one to XDMF, so ParaView and VisIt still read it.
Pre-release (0.2.x). The format may still change; each document records the
ymf version that wrote it (ymf:), and a reader refuses one from a newer ymf.
| Piece | State |
|---|---|
Archive data model, YAML I/O, structural checks (ymf.archive) |
Implemented. Tested against real Proteus output; used by Proteus 2.0 (its main line) |
.ymf → .xmf conversion (ymf2xmf, ymf.xdmf) |
Implemented. Lossless round trip; output opens in ParaView. Reads XDMF from other tools too, refusing by name what the model can't hold (docs/xdmf-model.yaml) |
| Self-contained archives: arrays inline, no HDF5 | Implemented. data_item_for(array, inline=True); write_archive.py --inline |
Problem-spec schema v0.2 (ymf.schema) |
Implemented. See the known gaps |
The notation, and strong form → advection-diffusion-reaction form (ymf.symbolic) |
Implemented. Specs run end to end in Proteus, reproducibly, via ymf_run |
Composing a spec from several files: a model extended into problems (ymf.compose) |
Implemented. Overrides are recorded in the composed document |
The archive of a spec: outputs keyed by their input, studies, make-like reruns (ymf.closure) |
Implemented. ymf_run writes and checks them; an archive can be extended |
Units, scales, dimensionless numbers (ymf.units) |
Implemented, apart from rewriting the PDE in dimensionless form |
Manning's n lookup and data-source catalog (ymf.data_sources) |
Implemented; data fetching is a stub |
| Parsing the weak forms; dimensionless rewriting of the equations | Planned. Weak forms are free text; the solver derives its weak form from the strong one |
| Dispatch to several solver backends | Planned |
Multi-component coupling through BMI (System: block) |
Deferred to v0.3 |
git clone git@github.com:proteustoolkit/ymf.git && cd ymf
pip install -e '.[dev]' # front-end, examples and tests
pytest
python examples/validate_spec.py
python examples/write_archive.py --outdir /tmp/ymf-demo
python examples/read_archive.py /tmp/ymf-demo/heat.ymf
ymf2xmf -v /tmp/ymf-demo/heat.ymf # then open /tmp/ymf-demo/heat.xmf in ParaViewA solver that only writes archives installs ymf with no extras, which
brings in pyyaml and nothing else. See examples/README.md for
what each example demonstrates.
A specification, abridged from examples/kovasznay_flow.ymf:
model:
name: "Kovasznay flow, Re = 40"
physical_model:
provenance: llm_derived
processes: [incompressible_flow]
assumptions: [steady, newtonian, constant_density, two_dimensional]
dimensionless_numbers:
Re: {formula: "ρ U L / μ"} # resolved to 40.0
strong_form:
unknowns: [{name: v, units: m/s, rank: 1}, {name: p, units: Pa}]
equations:
- "ρ ∂v/∂t + ∇·(ρ v⊗v) − μΔv + ∇p = 0 in Ω"
- "∇·v = 0 in Ω"
domain: "Ω = [-0.5, 1.0] × [-0.5, 1.5]"
boundary_conditions:
- {region: outer, variable: v, type: dirichlet,
formula: "(1 - exp(λx) cos(2πy), λ/(2π) exp(λx) sin(2πy))"}
coefficients:
ρ: {value: 1.0, units: kg/m3}
μ: {value: 0.025, units: Pa*s}
λ: "ρ/(2μ) - sqrt(ρ²/(4μ²) + 4π²)"
...
weak_forms:
- label: "mixed" # needs an inf-sup stable pair
- label: "stabilized_equal_order" # SUPG/PSPG
solution_paths:
discretizations:
- name: "taylor_hood"
from_weak_form: "mixed"
finite_element: {velocity: {family: CG, order: 2}, pressure: {family: CG, order: 1}}
- name: "equal_order_p1"
from_weak_form: "stabilized_equal_order"
finite_element: {velocity: {family: CG, order: 1}, pressure: {family: CG, order: 1}}Every equation, domain, region, coefficient and formula is written once,
in Unicode mathematics that ymf[symbolic] parses, so this file is also
what runs: Proteus's ymf_run solves it with each discretization and
measures the error against the exact solution
(Running a spec).
Writing an archive, from examples/write_archive.py:
from ymf.archive import (new_approximation, add_uniform_step, topology, geometry,
attribute, data_item_for, write_ymf)
domain = new_approximation("Mesh Spatial_Domain")
topo = topology("Triangle", len(elements), data_item_for(elements, "heat.h5:/elements"))
geom = geometry(data_item_for(nodes, "heat.h5:/nodes"))
for k, t in enumerate(times):
add_uniform_step(domain, t, topo, geom,
[attribute("T", data_item_for(T[k], "heat.h5:/T_t%d" % k))])
write_ymf(domain, "heat.ymf", extra=problem) # the spec: model, solution_paths, ...data_item_for reads shape, type and precision off the array, so the
metadata can't disagree with the data.
- Composable. A new branch adds weak forms or discretizations and never edits existing ones.
- Provenance everywhere. Each decision records whether it was
llm_derived,human_specified,human_editedorproteus_derived. - A trust boundary. Validation of human/LLM input is thorough and runs
once, in the front-end. The solver trusts validated input and checks
only structure, cheaply. The archive core therefore depends on
pyyamlalone. strictyaml measured ~27× slower than libyaml-backed pyyaml on archive metadata, too slow for a solver's write path. - XDMF is a consumer, not a dependency. The archive maps one-to-one
onto XDMF, and
.xmffiles are derived from it on demand, carrying the YMF-only content in a standard<Information>element that viewers ignore. - Additive evolution. Schema changes add optional fields where
possible. The one breaking change so far (structured
unknowns) comes withnormalize_unknowns().
src/ymf/
archive.py archive data model, YAML I/O, structural checks (pyyaml)
xdmf.py domain <-> XDMF conversion (stdlib)
closure.py the archive of a spec: output keys, studies (pyyaml)
cli.py the ymf2xmf command
normalize.py normalize_unknowns (stdlib)
schema.py problem-spec schema (strictyaml, ymf[spec])
compose.py `extends`: models, problems built on them (strictyaml, ymf[spec])
units.py unit checks, scales, dimensionless numbers (pint, ymf[units])
data_sources.py Manning's n lookup, data-source catalog (stdlib)
docs/ guides, design drafts, background reports -- start at docs/README.md
examples/ specs and runnable scripts -- start at examples/README.md
tests/ pytest suite, including real Proteus .xmf fixtures and the examples
import ymf loads only the pyyaml/stdlib modules. ymf.load_ymf,
ymf.check_units and the other front-end names load strictyaml or pint on
first use, so an archive-only install never needs them.
MIT (see LICENSE).