From 786dee42280066b47c2fe653af46a38f62315699 Mon Sep 17 00:00:00 2001 From: yannrichet Date: Sun, 14 Jun 2026 22:56:45 +0200 Subject: [PATCH 1/2] docs: OpenFOAM dam-break skill example + skill hardening from live runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the second worked skill example, skills/examples/openfoam-dambreak-random-sampling.md: the agent builds *both* an OpenFOAM (directory-case) wrapper and a custom random-sampling fzd algorithm from scratch — the "no official fz-* package" path, the inverse of the Newton-cooling example. Validated end to end against OpenFOAM v2412 (interFoam damBreak): an 8-sample obstacle-height study completes in ~30 s. Fold the lessons from running both examples back into the skill: - SKILL.md: a model never says how to *run* the code (that's the calculator) and its tell-tale failure (`Permission denied ./input.txt`); single-file vs case-directory inputs; an fzi prefix-collision check; a locale-safe output tip; a common-failures symptom table; links to both worked examples. - code-wrapper.md: a "Directory-tree (case-based) codes" section; locale-safe numeric output (`LC_ALL=C`); runner-script invocation gotchas (`bash ./script`, exec bit, self-cd). - reference.md: call out the fzd flag divergence (--input_dir/--input_vars, no --format). - howto.md: list the OpenFOAM example. The OpenFOAM example needs the recursive-staging fix (separate PR) to run. Co-Authored-By: Claude Fable 5 --- NEWS.md | 11 +- .../openfoam-dambreak-random-sampling.md | 282 ++++++++++++++++++ skills/fz/SKILL.md | 42 +++ skills/fz/code-wrapper.md | 30 ++ skills/fz/reference.md | 6 + skills/howto.md | 3 + 6 files changed, 372 insertions(+), 2 deletions(-) create mode 100644 skills/examples/openfoam-dambreak-random-sampling.md diff --git a/NEWS.md b/NEWS.md index fed5fa7..9cb904e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,9 +46,16 @@ ### Documentation +- Two worked skill examples in `skills/examples/`: a Newton-cooling calibration (reuse the + Modelica wrapper + brent algorithm) and an OpenFOAM dam-break random-sampling study + (author the wrapper *and* the algorithm from scratch). The latter is validated end to end + against OpenFOAM v2412. +- Skill guidance hardened from running those examples: model-vs-calculator distinction, + directory-tree (case-based) codes, locale-safe (`LC_ALL=C`) output parsing, runner-script + invocation gotchas, the `fzd` flag divergence, and a common-failures table. - New Agent Skill in `skills/fz/` for AI coding agents (Claude Code and compatible), - with a condensed API/CLI reference, fzd algorithm guide, and wrapper implementation - guide (`wrapper.md`). + with a condensed API/CLI reference, fzd algorithm guide, and code-wrapper guide + (`code-wrapper.md`). - The repo is now a Claude Code plugin marketplace (`.claude-plugin/`): install the skill from inside Claude Code with `/plugin marketplace add Funz/fz` + `/plugin install fz@funz`. - New `llms.txt` documentation index and `CLAUDE.md` contributor guide. diff --git a/skills/examples/openfoam-dambreak-random-sampling.md b/skills/examples/openfoam-dambreak-random-sampling.md new file mode 100644 index 0000000..f4b4334 --- /dev/null +++ b/skills/examples/openfoam-dambreak-random-sampling.md @@ -0,0 +1,282 @@ +# Example: random-sampling an OpenFOAM dam break — wrapper *and* algorithm built live + +A worked example of using the **fz skill** with an AI coding agent (Claude Code) on a code +that has **no ready-made fz package**. It is the deliberate opposite of +[newton-cooling-calibration.md](newton-cooling-calibration.md), where the agent installed an +official Modelica wrapper and brent algorithm: here **both pieces are authored from +scratch**, in the working directory, because nothing exists to install — + +- the **OpenFOAM coupling** — a code wrapper (model + runner + calculator), following + [../fz/code-wrapper.md](../fz/code-wrapper.md); +- the **random sampling** — a custom `fzd` algorithm, following + [../fz/algorithm-wrapper.md](../fz/algorithm-wrapper.md). + +So this is the path the skill takes when its step-0 check ("is there an official wrapper?") +comes back empty: build the binding yourself, verify it cheaply, then run the study. + +> **Validated end to end** against OpenFOAM v2412 (`interFoam`) and the bundled +> `damBreak` tutorial: the wrapper and the custom algorithm below were run as written — +> an 8-sample study completes in ~30 s and yields a peak water height varying ~0.08–0.18 m +> with obstacle height. OpenFOAM-internal names (paths, the `blockMeshDict` obstacle level, +> the function-object column) are still **distribution/version dependent** — confirm them +> in your install. Two `fz` framework fixes were needed for a directory-tree code like +> OpenFOAM to run at all (recursive staging of case subdirectories into and out of the run +> directory); use a build that includes them. + +## The engineering problem + +The [OpenFOAM "breaking of a dam" tutorial](https://www.openfoam.com/documentation/tutorial-guide/4-multiphase-flow/4.1-breaking-of-a-dam) +(`damBreak`, the `interFoam` VOF multiphase solver) releases a column of water that +collapses across a tank. The tank floor carries a small **obstacle**. We treat the +**obstacle height** as uncertain and do a **random (Monte-Carlo) sampling** of it to study +how it affects a quantity of interest — here the **peak water height** reached at a +downstream location. + +## Prerequisites + +- **OpenFOAM** installed and its environment **sourced** (`blockMesh`, `setFields`, and the + solver — `interFoam` or `foamRun` depending on your distribution — on PATH). The tutorial + case ships under `$FOAM_TUTORIALS/multiphase/interFoam/laminar/damBreak/damBreak`. +- `fz` on PATH (`pip install 'funz-fz>=1.0'`) and the **fz skill** installed (see + [../howto.md](../howto.md)). +- `claude` CLI, logged in or `ANTHROPIC_API_KEY` set. + +Work in a scratch directory **outside any git repository** (Claude Code resolves its +project at the enclosing repo root, which would otherwise hide a project-level skill): + +```bash +SANDBOX=$(mktemp -d) && cd "$SANDBOX" +``` + +## Solve it in one ask + +Describe the problem and make the two "build it yourself" requirements explicit, so the +agent doesn't waste a turn hunting for a plugin that isn't there: + +```bash +claude -p "Using the fz skill, run a random-sampling study on the OpenFOAM 'damBreak' +tutorial (interFoam). There is NO official fz wrapper for OpenFOAM and NO installable +sampling algorithm — implement both yourself in this directory: + +1. Wrap the case as a reusable fz model (follow the skill's code-wrapper guide): copy the + damBreak tutorial, parameterize the obstacle height in system/blockMeshDict, write the + model JSON + a runner script that runs blockMesh and the solver + a local calculator + alias. Mind the variable-prefix collision: OpenFOAM uses '\$' for its own macros, so + choose a different fz varprefix (e.g. '%'). Pick a scalar output: the peak water height + at a downstream location (add an interfaceHeight function object if needed). +2. Write a custom fzd random-sampling algorithm (follow the skill's algorithm-wrapper + guide) that draws N uniform samples of the obstacle height in one batch. + +Verify the wrapper on a single height before the study. Then sample the obstacle height +(20 draws, seed 1) over a sensible range and report mean/std of the peak height; write the +per-sample results to results.json (records of obstacle_height, peak_height, status)." \ + --allowedTools "Bash,Read,Write,Edit,Glob,Grep,Skill" --max-turns 120 +``` + +## The path the agent follows + +### 0. No official wrapper → author one + +The skill's step 0 is *check for an official wrapper*. `fz install model openfoam` resolves +to `github.com/Funz/fz-openfoam`, which does not exist — so the agent falls through to the +authoring path ([code-wrapper.md](../fz/code-wrapper.md)) instead of a one-line install. + +### 1. Get the case + +```bash +cp -r "$FOAM_TUTORIALS/multiphase/interFoam/laminar/damBreak/damBreak" case +# conda-forge OpenFOAM leaves FOAM_TUTORIALS unset; use $WM_PROJECT_DIR/tutorials/... there +``` + +### 2. Parameterize the obstacle height — mind the prefix collision + +In this geometry the obstacle is the **un-meshed floor block** between `x = 2` and +`x = 2.16438`: the `blocks` list simply omits it, so the mesh flows around a solid step +whose top sits at the shared y-level `0.32876` (× `scale 0.146` ≈ 0.048 m). Raising or +lowering that level *is* changing the obstacle height, so the agent replaces every +occurrence of `0.32876` in `system/blockMeshDict` with the fz variable: + +```bash +sed -i 's/0.32876/%obstacle_height/g' case/system/blockMeshDict # 8 vertex rows +``` + +**OpenFOAM already uses `$` for macro expansion**, so reusing fz's default `$` prefix would +clash — the agent picks a non-colliding prefix (`%`), exactly the situation +[code-wrapper.md](../fz/code-wrapper.md) warns about: + +```c++ +// system/blockMeshDict (the 8 vertices at the obstacle-top level) + (0 %obstacle_height 0) + (2 %obstacle_height 0) + (2.16438 %obstacle_height 0) + (4 %obstacle_height 0) + // ... and the same four at z = 0.1 +``` + +(`%obstacle_height` is in `blockMeshDict` units — multiplied by `scale`; default `0.32876`, +sampled below over `[0.2; 0.5]`. fz's `{}`/`@` formula syntax is untouched: OpenFOAM never +writes `@{...}`, so no formula is triggered.) + +### 3. Define the QoI and the model + +The base case writes no scalar result, so the agent enables an `interfaceHeight` function +object to record the water height at two downstream probes. `controlDict` already ends with +`functions { #sinclude "sampling" }`, so just drop in `system/sampling`: + +```c++ +// system/sampling +interfaceHeight1 +{ + type interfaceHeight; + libs (fieldFunctionObjects); + alpha alpha.water; + locations ((0.35 0 0.005) (0.45 0 0.005)); // just downstream of the obstacle + writeControl timeStep; + writeInterval 20; +} +``` + +It writes `postProcessing/interfaceHeight1/0/height.dat` with columns +`Time, hB hL (loc0), hB hL (loc1)`; the QoI is the peak `hB` at the first probe (column 2). + +`.fz/models/OpenFOAM.json` — `varprefix: "%"`, `commentline: "//"`, and a scalar +`peak_height` output: + +```json +{ + "id": "OpenFOAM", + "varprefix": "%", + "commentline": "//", + "output": { + "peak_height": "LC_ALL=C awk '!/^#/ && NF {v=$2+0; if (v>m) m=v} END {print m}' postProcessing/interfaceHeight1/0/height.dat" + } +} +``` + +The output command runs **inside each case's result directory** after the solver; its +stdout is auto-cast to a number. Two robustness points learned by actually running this: +**`LC_ALL=C`** forces a period decimal separator — without it, on a non-English locale a +plain `sort -g` mis-orders the scientific-notation values and returns a near-zero "max"; +and computing the max in `awk` (rather than `sort | tail`) keeps it to one pass. + +### 4. The runner script + +`.fz/calculators/OpenFOAM.sh` — runs the meshing and the solver in the compiled case dir. +Using the tutorial's own `Allrun` keeps it solver-version agnostic (`Allrun` reads the +solver name from `controlDict`): + +```bash +#!/bin/bash +# OpenFOAM.sh — fz runner for the damBreak wrapper. Usage: OpenFOAM.sh +[ -d "$1" ] && cd "$1" +command -v blockMesh >/dev/null || { echo "OpenFOAM environment not sourced" >&2; exit 2; } + +bash ./Allrun > log.Allrun 2>&1 # blockMesh + setFields + the case's solver + +# fail loudly if the quantity of interest was not produced, so the case is marked error +ls postProcessing/interfaceHeight1/*/height.dat >/dev/null 2>&1 || { echo "no interfaceHeight output" >&2; exit 1; } +``` + +(Invoke it as `bash ./Allrun`, not `bash Allrun`: `Allrun`'s first line is +`cd "${0%/*}"`, which with a bare name becomes `cd Allrun` and aborts the script.) + +### 5. The local calculator alias + +`.fz/calculators/localhost_OpenFOAM.json` — wires the model **id** to the runner so fz can +auto-discover it: + +```json +{ + "uri": "sh://", + "models": { "OpenFOAM": "bash .fz/calculators/OpenFOAM.sh" } +} +``` + +### 6. Verify the wrapper on one height (definition of done) + +Per the skill, prove the binding works on a single case — the cheap-failure gate — before +the study. No `--calculators` is needed; the installed alias is auto-discovered from the +model id: + +```bash +fzi --input_path case --model OpenFOAM --format json # finds: obstacle_height +fzr --input_path case --model OpenFOAM \ + --input_variables '{"obstacle_height": 0.33}' --format json # one full run; peak_height parses? +``` + +### 7. Author the random-sampling algorithm + +`.fz/algorithms/random_sampling.py` — a one-shot `fzd` algorithm +([algorithm-wrapper.md](../fz/algorithm-wrapper.md)): propose all N points up front, then +stop. `input_vars` arrives as `{name: (min, max)}`; `get_next_design` returning `[]` ends +the loop; `get_analysis` summarizes: + +```python +#title: Uniform random sampling (Monte Carlo) +#author: example +#options: n=20;seed=1 + +import random + +class RandomSampling: + def __init__(self, **options): + self.n = int(options.get("n", 20)) + self.seed = int(options.get("seed", 1)) + + def get_initial_design(self, input_vars, output_vars): + rng = random.Random(self.seed) + return [{v: rng.uniform(lo, hi) for v, (lo, hi) in input_vars.items()} + for _ in range(self.n)] + + def get_next_design(self, previous_input_vars, previous_output_values): + return [] # one-shot: every point was proposed in the initial design + + def get_analysis(self, input_vars, output_values): + ys = [y for y in output_values if y is not None] # None = a failed case + n = len(ys) + mean = sum(ys) / n if n else float("nan") + std = (sum((y - mean) ** 2 for y in ys) / n) ** 0.5 if n else float("nan") + return { + "text": f"random sampling: {n} valid samples, mean={mean:.4g}, std={std:.4g}", + "data": {"n": n, "mean": mean, "std": std, + "min": min(ys, default=None), "max": max(ys, default=None)}, + } +``` + +### 8. Run the study + +```bash +fzd --input_dir case --model OpenFOAM \ + --input_vars '{"obstacle_height": "[0.2; 0.5]"}' \ + --output_expression "peak_height" \ + --algorithm .fz/algorithms/random_sampling.py \ + --options '{"n": 20, "seed": 1}' \ + --results_dir study +``` + +`fzd` evaluates the sampled heights (each a full CFD run), records `peak_height` per sample +under `study/`, and prints the `get_analysis` summary (mean/std of the peak height). The +agent collects the per-sample records into `results.json`. As validated, an 8-sample run +finishes in ~30 s with peaks spread over ~0.08–0.18 m (mean ≈ 0.12, std ≈ 0.04). + +## Notes + +- **The prefix collision is the lesson here.** Because OpenFOAM owns `$`, the wrapper must + set a different `varprefix`. The agent confirms its choice with `fzi` — it should report + exactly `obstacle_height`, not stray `$`-macros from the OpenFOAM dictionaries. +- **CFD cost scales with `n`.** This tiny damBreak case runs in ~3 s, so 20 samples is a + minute; a real case is far heavier. Run samples concurrently by supplying several + calculators + (`--calculators '["localhost_OpenFOAM", "localhost_OpenFOAM", "localhost_OpenFOAM"]'`), + or lower `n` while developing. fz deduplicates and caches across runs, so re-running with + a larger `n` reuses what was already computed. +- **Random sampling vs. `fzr`.** A fixed Monte-Carlo list could also be run with + `fzr --input_variables '{"obstacle_height": []}'`. Authoring it as an + `fzd` algorithm instead is what makes the *sampling logic itself* a reusable, swappable + component — and is the point this example demonstrates. +- **Promote it later.** Once verified, the two artifacts are exactly what the `fz-` + packaging conventions expect: the case + `.fz/` could become an installable + `fz-openfoam` model, and `random_sampling.py` an installable `fz-randomsampling` + algorithm (see [code-wrapper.md](../fz/code-wrapper.md) and + [algorithm-wrapper.md](../fz/algorithm-wrapper.md)). This example is how a wrapper begins + before it is published. diff --git a/skills/fz/SKILL.md b/skills/fz/SKILL.md index 4452771..17bf2e3 100644 --- a/skills/fz/SKILL.md +++ b/skills/fz/SKILL.md @@ -39,6 +39,14 @@ before moving to the next — this isolates errors cheaply instead of debugging 6. **Verify output parsing**: `fzo` on the directory containing the outputs. 7. **Run the study**: `fzr` with the full variable grid and calculator(s). +**Single file vs. a case directory.** `input_path` can be one file or a whole directory +tree. Codes like OpenFOAM or Telemac take a *case directory* (`system/`, `constant/`, `0/`, +…); point `input_path` at the directory, parameterize whichever file(s) hold the values, +and the calculator runs inside the per-case copy with the tree intact. Authoring such a +wrapper has extra gotchas (prefix collisions, directory I/O, locale) — see +[code-wrapper.md](code-wrapper.md). (Recursive staging of case subdirectories requires +fz from git main / the next release; fz 1.0 on PyPI stages only top-level files.) + ### 0. Check for an existing wrapper first Funz publishes ready-made wrappers for common simulation codes (Modelica, MORET, …) as @@ -120,11 +128,19 @@ Output command results are auto-cast to Python literals (int, float, list, dict) possible. For reusability, save as `.fz/models/perfectgas.json` (project) or `~/.fz/models/perfectgas.json` (global) and refer to it by alias: `model="perfectgas"`. +> **A model never says how to *run* the code.** It declares only the input syntax and the +> output parsers. *Where and how* the simulation executes is the calculator's job (see +> "Calculators" below). There is no `run` field in a model. If you forget the calculator, +> fz falls back to `sh://` and tries to execute the input file itself — the tell-tale +> symptom is `Permission denied ... ./input.txt`. + ### 3–6. Verify each step before the full run ```bash # 3. Variables found? (returns variables as keys, None values) fzi --input_path input.txt --model perfectgas --format json +# Must list EXACTLY your variables. Stray names (e.g. the code's own $-macros) mean your +# varprefix collides with the code's syntax — change it (e.g. varprefix="%") and re-check. # 4. Compilation correct for one case? fzc --input_path input.txt --model perfectgas \ @@ -262,6 +278,10 @@ read [algorithm-wrapper.md](algorithm-wrapper.md). - In `output` parsing commands, awk field references like `$3` must survive shell quoting: in JSON model files write them normally; inside a single-quoted inline `--model` JSON argument they are safe as-is, but never wrap them in double quotes on the shell. +- Make numeric `output` commands **locale-independent**: prefix with `LC_ALL=C`. On a + non-English locale, tools like `sort -g` use a comma decimal and silently mis-order + period/scientific-notation values (returning a wrong "max"); computing min/max in `awk` + (one pass) is more robust than `sort | head/tail`. - `fzr` argument order in Python is `(input_path, input_variables, model, results_dir=..., calculators=...)` — use keyword arguments to stay safe. - Concurrency: repeat the same calculator URI N times (or set `FZ_MAX_WORKERS`) to run N @@ -270,3 +290,25 @@ read [algorithm-wrapper.md](algorithm-wrapper.md). per-case `out.txt`/`err.txt`; on interrupt, partial results survive and `cache://` resumes. - Full API and CLI details, environment variables, and the model/calculator JSON schemas: see [reference.md](reference.md). + +## Common failures (read err.txt/log.txt in the case dir first) + +| Symptom | Likely cause | +|---------|--------------| +| `Permission denied ... ./input.txt` | No calculator given; fz tried to execute the input. Add a calculator (`sh://bash runner.sh`). | +| All cases `failed`, `N calculator failures` | The calculator command itself errors — read the case's `err.txt`/`log.txt`. | +| Output column is `null` but case is `done` | Output command matched nothing: wrong path/field, missing subdir output (see directory codes), locale (`LC_ALL=C`), or `python` vs `python3`. | +| `fzi` lists extra/unexpected variables | `varprefix` collides with the code's own syntax — change it. | +| `fzd` runs an empty `sh://` / every case fails | On fz 1.0 PyPI, `fzd` doesn't auto-discover calculators — pass them explicitly (fixed in git main). | + +## Worked examples + +Two end-to-end walkthroughs (natural-language ask + the path the agent follows, with +checkable outcomes), one per archetype: + +- [../examples/newton-cooling-calibration.md](../examples/newton-cooling-calibration.md) — + **reuse ready-made packages**: discover the Modelica wrapper + brent algorithm to solve + an inverse problem. +- [../examples/openfoam-dambreak-random-sampling.md](../examples/openfoam-dambreak-random-sampling.md) — + **build from scratch**: author an OpenFOAM (directory-case) wrapper and a custom + random-sampling algorithm when no `fz-*` package exists. diff --git a/skills/fz/code-wrapper.md b/skills/fz/code-wrapper.md index 3344a2b..cf183d7 100644 --- a/skills/fz/code-wrapper.md +++ b/skills/fz/code-wrapper.md @@ -69,6 +69,10 @@ Rules and choices: a JSON dict/list (as `trajectory` above) gives structured columns in the results DataFrame. - Prefer `python3` over `python` in output commands, and keep dependencies minimal — these commands run on the user's machine, not yours. +- Make numeric extraction **locale-independent**: prefix the command with `LC_ALL=C`. + Under a non-English locale (comma decimal), `sort -g` mis-orders period/scientific-notation + numbers and can return a wrong "max"; prefer a one-pass `awk` max/min, e.g. + `LC_ALL=C awk '!/^#/ && NF {if ($2+0>m) m=$2+0} END {print m}' out/series.dat`. ## 2. The runner script @@ -100,6 +104,14 @@ rm -f PID [ $status -eq 0 ] && [ -f out/results.log ] || exit 1 ``` +Two invocation gotchas when the runner calls a *bundled* script (e.g. the code's own +`Allrun`/`Allclean`): + +- The executable bit is not guaranteed to survive staging — invoke bundled scripts as + `bash ./script`, not `./script`. +- Use `bash ./script` (with `./`), not `bash script`: a script whose first line self-cds + with `cd "${0%/*}"` becomes `cd script` (no slash to strip) and aborts. + ## 3. The local calculator alias The runner script alone is NOT enough — fz discovers calculators through **JSON alias @@ -119,6 +131,24 @@ file installed, `fzr --model MyCode ...` without `--calculators` finds and uses automatically. Users add their own `ssh://`/`slurm://` aliases with the same `models` key for remote execution. +## Directory-tree (case-based) codes + +Some codes (OpenFOAM, Telemac, …) take a whole **case directory** (`system/`, `constant/`, +`0/`, …) rather than one file. The wrapper pattern is the same, with three adjustments: + +- Point `input_path` at the case directory. Parameterize whichever file(s) hold the values + (e.g. an OpenFOAM `system/blockMeshDict`); fz compiles every file in the tree. +- The runner runs **inside the per-case copy of the tree** — it can `cd` in and call the + code's own driver (`bash ./Allrun`), and read/write subdirectories freely. +- The output parser reads the result *and any output subdirectory* the run produced + (e.g. `postProcessing/.../*.dat`). + +Requires recursive staging of case subdirectories, which is fz from git main / the next +release; fz 1.0 on PyPI stages only top-level files into the run directory, so a case with +subdirectories will fail there. The +[OpenFOAM dam-break example](../examples/openfoam-dambreak-random-sampling.md) is a complete +worked wrapper of this kind. + **Definition of done** — the wrapper is finished only when both hold: 1. `fz list --check --format json` shows the model AND a calculator supporting it; diff --git a/skills/fz/reference.md b/skills/fz/reference.md index b2f71dd..a9c6581 100644 --- a/skills/fz/reference.md +++ b/skills/fz/reference.md @@ -111,6 +111,12 @@ fzd --input_dir/-i --input_vars/-v --model/-m --output_expression/-e --algorithm/-a --results_dir/-r --calculators/-c --options/-o ``` +> **`fzd` flag divergence (easy to trip on):** unlike `fzi`/`fzc`/`fzr`, `fzd` names the +> input flags `--input_dir`/`-i` and `--input_vars`/`-v` (not `--input_path`/ +> `--input_variables`), takes algorithm options via `--options`/`-o`, and has **no +> `--format`** — it prints a convergence summary and writes the design/analysis under +> `--results_dir`. `--input_dir` accepts a single file as well as a directory. + - In fz 1.0 (current PyPI release), paths must be given via flag (`fzi --input_path input.txt -m mymodel`) and only the canonical flag names below work. Newer fz (git main / next release) additionally accepts positional paths diff --git a/skills/howto.md b/skills/howto.md index e376321..7d2c0df 100644 --- a/skills/howto.md +++ b/skills/howto.md @@ -108,6 +108,9 @@ the path the agent follows, with checkable outcomes): - [examples/newton-cooling-calibration.md](examples/newton-cooling-calibration.md) — calibrate a Newton's-law-of-cooling model: the agent discovers the Modelica wrapper and the brent algorithm to solve a 1-parameter inverse problem. +- [examples/openfoam-dambreak-random-sampling.md](examples/openfoam-dambreak-random-sampling.md) — + random-sample the obstacle height in an OpenFOAM dam break: the agent builds *both* the + code wrapper and the sampling algorithm from scratch (the "no official wrapper" path). ## What's in the skill From ba81cfe6d5675849e1a0869fddb320e13e044fc5 Mon Sep 17 00:00:00 2001 From: yannrichet Date: Mon, 15 Jun 2026 00:00:08 +0200 Subject: [PATCH 2/2] docs: note fzd's --input_path/--input_variables aliases (version-aware) Soften the fzd flag-divergence note: newer fz also accepts the fzi/fzc/fzr input flag names as aliases (see the fzd alias PR); fz 1.0 on PyPI takes only the canonical --input_dir/--input_vars. The no---format point is unchanged. Co-Authored-By: Claude Fable 5 --- skills/fz/reference.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/skills/fz/reference.md b/skills/fz/reference.md index a9c6581..0e8550d 100644 --- a/skills/fz/reference.md +++ b/skills/fz/reference.md @@ -111,11 +111,13 @@ fzd --input_dir/-i --input_vars/-v --model/-m --output_expression/-e --algorithm/-a --results_dir/-r --calculators/-c --options/-o ``` -> **`fzd` flag divergence (easy to trip on):** unlike `fzi`/`fzc`/`fzr`, `fzd` names the -> input flags `--input_dir`/`-i` and `--input_vars`/`-v` (not `--input_path`/ -> `--input_variables`), takes algorithm options via `--options`/`-o`, and has **no -> `--format`** — it prints a convergence summary and writes the design/analysis under -> `--results_dir`. `--input_dir` accepts a single file as well as a directory. +> **`fzd` flag divergence (easy to trip on):** `fzd`'s canonical input flags are +> `--input_dir`/`-i` and `--input_vars`/`-v` (in newer fz — git main / next release — it +> also accepts the `fzi`/`fzc`/`fzr` names `--input_path` and `--input_variables` as +> aliases; fz 1.0 on PyPI takes only the canonical names). It takes algorithm options via +> `--options`/`-o`, and has **no `--format`** — it prints a convergence summary and writes +> the design/analysis under `--results_dir`. `--input_dir` accepts a single file as well as +> a directory. - In fz 1.0 (current PyPI release), paths must be given via flag (`fzi --input_path input.txt -m mymodel`) and only the canonical flag names below work.