Convection Workflow — Developer Guide¶
How the Rayleigh-Bénard convection workflow is built on top of
underworld3.workflows. Companion to the
user guide, which covers what the workflow
does rather than how it’s wired.
The convection workflow is the worked-example consumer of the
underworld3.workflows package. Reading this guide alongside the
source files (~1500 lines, listed below) shows how each abstraction
in uw.workflows lands in a real workflow. A new workflow author
would copy this directory and rewrite the physics; the surrounding
mechanics — config class, step decorators, runner cascade, recipes,
CLI driver — stay essentially the same.
Module map¶
File |
What lives there |
|---|---|
|
|
|
|
|
Frame rendering and movie encoding (not yet a workflow_step — see “Open work” below) |
|
CLI driver, auto-derived from |
|
Recipe: project T from a prior run onto a new mesh / degree as a seeded IC |
|
Recipe: chain warm-starts through a sequence of Ra values |
Workflow DAGs¶
Per-case workflow (convection_config)¶
A “case” is one model run: one ConvectionConfig instance, one
output directory, one steady-state termination. In the sweep
context, every (rayleigh, aspect_ratio) combination is a case.
predict_bl_thickness ──▶ bl_thickness ──┐
predict_mean_T ──────▶ mean_T_predicted │
│
create_mesh ─▶ mesh ──┬──▶ create_solvers ──▶ stokes, adv_diff, T, v, p
│ │
└────────────────────────────────────┤
▼
evolve ──▶ run_directory, evolution_log
│
▼
summarise_run ──▶ run_summary
Auto-generated DOT source from runner.diagram():
from underworld3.workflows import diagram
import convection_config as cc
print(diagram(cc))
# → render with `dot -Tpng - -o convection_dag.png`
Sweep workflow (convection_sweep)¶
run_sweep ──▶ all_cells_completed ──┬──▶ tabulate_nu_vs_ra ──▶ nu_vs_ra_csv ──▶ plot_nu_vs_ra ──▶ nu_vs_ra_plot
│
└──▶ tabulate_vrms_vs_ra ──▶ vrms_vs_ra_csv ──▶ plot_vrms_vs_ra ──▶ vrms_vs_ra_plot
run_sweep is the bridge between the two workflows. This
particular sweep grid is (rayleigh × aspect_ratio) because those
are the diagnostics canonically swept in Rayleigh-Bénard work; the
framework imposes no specific axes. For each case in the grid,
run_sweep instantiates a nested
WorkflowRunner(convection_config, case_config, products=...) and
calls runner.build("run_summary"). Each case’s products land in
its own <case_dir>/products/manifest.yaml; the sweep’s outer
products (CSV tables, plots) land in <output_dir>/products/manifest.yaml.
Note: the product name all_cells_completed and the helper
_cell_key() in the source use “cell” in the spreadsheet sense
(one entry in the parameter grid). Prose in this doc uses “case”
to avoid collision with “convection cell” in the physics literature.
Step reference¶
Per-case steps (in convection_config)¶
Step |
Produces |
Requires |
Notes |
|---|---|---|---|
|
|
— |
high-Ra 2D scaling, |
|
|
— |
|
|
|
— |
builds an |
|
|
|
Taylor-Hood (v=2, p=1), T at |
|
|
|
idempotent: short-circuits on |
|
|
|
averages over the steady window, writes |
Sweep steps (in convection_sweep)¶
Step |
Produces |
Requires |
Notes |
|---|---|---|---|
|
|
— |
iterates |
|
|
|
tidy CSV columns: |
|
|
|
same shape for Vrms |
|
|
|
log-log Nu(Ra), one curve per aspect_ratio, with |
|
|
|
log-log Vrms(Ra), one curve per aspect_ratio |
Config classes¶
ConvectionConfig — per-case parameters¶
Identity fields (declared in _identity_fields, fold into cache_key):
aspect_ratio, cellsize, qdegree, regular, T_degree, rayleigh, viscosity, diffusivity, T_top, T_bottom
The non-dimensional Boussinesq formulation pins viscosity = 1,
diffusivity = 1, T_top = 0, T_bottom = 1 — Ra absorbs the
viscous + thermal scales, and the boundary temperatures set the
temperature contrast. These four fields stay in the config (so a
developer who really wants to tweak them — e.g. shift the T scale
to [-0.5, 0.5] to mask AdvDiff drift, per the in-code comment) and
in _identity_fields (so the tweak invalidates the cache).
simulate.py suppresses them from the CLI’s --help so they don’t
present as user knobs; the user-facing doc table omits them.
qdegree is also pinned by a @model_validator(mode='after') —
it’s structurally a function of the highest variable degree
(currently T_degree, since v=2 and p=1 are dominated by T at
default T_degree=3). Even if a caller passes qdegree to the
constructor, the validator overrides it to max(2, T_degree).
Field stays in the config so it lands in the manifest snapshot;
not user-tunable. Hidden from the CLI for the same reason as the
non-dim quartet above.
Operational fields (changeable between invocations without invalidating cached products):
Field |
Default |
Purpose |
|---|---|---|
|
|
Fraction of timeseries used for stationarity test |
|
|
Tolerance on between-halves mean drift |
|
|
Tolerance on coefficient of variation |
|
|
Minimum sample count for steady test |
|
|
(currently informational; not used in |
|
|
Hard step cap |
|
|
h5 checkpoint cadence |
|
|
Multiplier on |
|
|
Heavy-diagnostic cadence (0 = match |
|
|
Clip T to |
|
|
Per-case run directory |
|
|
One of |
Timeseries column schema: step, t, dt, Nu_top, Nu_bot, Vrms, Vmax, mean_T.
SweepConfig — sweep grid + per-case defaults¶
The sweep axes are workflow-specific. This convection workflow
sweeps over Rayleigh number and aspect ratio (rayleigh_values
and aspect_ratios lists). Other workflows would expose
different lists.
Identity fields (the grid axes plus per-case mesh + physics):
rayleigh_values, aspect_ratios, cellsize, qdegree, regular, viscosity, diffusivity, T_top, T_bottom
Note: _IDENTITY_FIELDS does not include T_degree — current
SweepConfig doesn’t expose T_degree as a sweep field. Adding it
would mean exposing T_degree as a SweepConfig attribute and
forwarding it in _per_run_config.
How each uw.workflows primitive lands¶
Primitive |
Where convection uses it |
|---|---|
|
|
|
declared on both configs; aliases |
|
called by the runner during |
|
every step in |
|
|
|
per-case at |
|
wraps every per-case run directory; |
|
the case’s |
|
the |
|
|
|
|
|
|
|
|
|
|
|
available; documentation generator could embed |
|
available; not yet wired into the example notebook (UI hooks for future widgets) |
On-disk layout (per-case)¶
<output_dir>/
manifest.yaml # Run-directory manifest (workflow + config_hash + workflow_api)
timeseries.csv # Append-only per-step diagnostics
run_summary.yaml # Only when status="steady"
run.mesh.NNNNN.{h5,xdmf} # Mesh + per-variable h5 chain
run.mesh.T.NNNNN.h5
run.mesh.U.NNNNN.h5
products/
manifest.yaml # WorkflowProducts manifest (per-product cache_key + inputs audit)
mesh.mesh.00000.h5 # Mesh product (separate from run.mesh)
mesh.mesh.00000.xdmf
bl_thickness.yaml # Float, yaml-fallback persistence
evolution_log.yaml # List-of-dicts, yaml-fallback
run_summary.yaml # Dict, yaml-fallback (mirrors the run-dir's summary)
The two manifest.yaml files in the same tree look similar but
serve different roles:
The outer
manifest.yaml(next to the h5 chain) is the Run-directory’s identity card — workflow name, config_hash, config_snapshot,workflow_api. Exists for any run.The inner
products/manifest.yamlis the registered workflow-product graph for this case —cache_key+inputsper product. Created whenWorkflowRunner(..., products=...)is used.
When a case’s identity changes (e.g. T_degree bump), the outer
manifest’s config_hash mismatch triggers _check_compatibility’s
archive-or-error logic. The products manifest’s cache_keys
independently mismatch, triggering the runner’s per-product rebuild.
Sweep-level layout¶
<sweep_output_dir>/
aspect_1x1/Ra1e3/... # Per-case run directory (see above)
aspect_1x1/Ra1e4/...
aspect_4x1/Ra1e3/...
...
tables/
nu_vs_ra.csv # Tidy aggregation
vrms_vs_ra.csv
figures/
nu_vs_ra.png # log-log Nu(Ra)
vrms_vs_ra.png
products/
manifest.yaml # Sweep-level products: all_cells_completed, *_csv, *_plot
all_cells_completed.yaml
Recipes¶
warm_start and ramp live in this directory but are not part
of the uw.workflows API. They are example scripts that compose
the public primitives (Run.open, Run.load_field, Run.create,
Run.append_step, WorkflowRunner.build). Promotion to API
happens “if 3+ apps end up writing nearly-identical versions” — the
discipline keeps the public surface small while letting per-app
patterns live where they belong.
warm_start(source_dir, target_dir, **target_overrides)¶
Open the source
Run, find its latest checkpoint.Build a fresh target mesh + MeshVariables at the target’s degrees.
Project source’s T into target’s T via
Run.load_field(kd-tree interpolation handles different mesh / different FE space).Solve Stokes once for a consistent v.
Persist the seeded state as step 0 of the target run via
Run.append_step, write the manifest withwarm_start_sourcemetadata.
Open work¶
convection_visualise.render_temperature_frames/render_tracer_frames/encode_movieare not yet@workflow_step-decorated. Decorating them would addtemperature_frames,tracer_frames,temperature_movie,tracer_movieas products with cache_keys derived from the case’srun_directorycache_key. Re-running the notebook would then short-circuit movie rendering when the underlying h5 chain hasn’t grown.The visualisation-side
(run_dir / "run_summary.yaml").exists()check inconvection_notebook.make_movies_forcould becomeRun(run_dir).summary is not None.T_degreeis aConvectionConfigfield but not aSweepConfigfield, so the sweep can’t currently sweep over discretisation orders.H2ExConfigdoesn’t declare_identity_fields, so its products fall back to existence-based caching. Bringing it up to par is about an hour of work.
Rebuilding this guide¶
The structural content of this guide (steps, fields, identity
tuples, on-disk layout) can be regenerated by introspecting
convection_config and convection_sweep. A future
generate_developer_doc.py would emit this file modulo prose
sections. For now, hand-maintained — bump the relevant table when
adding/removing a step or field.