1"""LE4 Layer X baseline ("golden") burns.
3Runs Layer X in-process (no HTTP server) at today's default settings and reduces each burn to
4the numbers a design review reads. The JSON it writes is the reference for the overnight rule
5"a change that moves the LE4 baseline by more than 1 % must be reported"; the slow test
6``tests/test_layerx_golden.py`` holds the pad helium case to it.
8Cases (the engine is LE4, ``configs/ethalox_6800N.yaml`` unless ``--config`` says otherwise)::
10 he_pad copv_study_he on the pad, erosion replay on (the hot-fire drawing)
11 he_flight the same, flown at the design's liftoff mass (``liftoff_mass_kg`` unset: the
12 config's airframe plus motor, propellant and gases)
13 gn2_pad copv_study_gn2 on the pad (the drawing the Layer X tab selects by default)
15Supplementary (``--all`` or ``--case``)::
17 gn2_flight copv_study_gn2 flown: the tab's out-of-the-box run (the UI defaults flight on)
18 he_flight_he_ullage he_flight with config.{lox,fuel}_tank.ullage_gas = Helium in the run's copy
19 of the config: a what-if, because at defaults the flight prices the ullage
20 refill as nitrogen and refuses the helium bottle (see CASES)
22Settings are ``LayerXSettings``'s defaults except ``drawing_id``, ``flight`` and ``--dt``. The UI's
23``DEFAULT_SETTINGS`` (frontend/src/api/layerx.ts) equal them except that the UI turns ``flight``
24on; the pad cases are therefore one toggle away from the tab's out-of-the-box run.
26The run is the router's (backend/routers/layerx.py ``start_run``): a private deepcopy of the
27config, its own ``PintleEngineRunner`` (so the Forward cross-check runs), no restated drawing
28parameters, ``run_prepared(prep, runner=..., replay=settings.replay, config=...)``.
33 PYTHONPATH=../lib/stardesign python3 scripts/layerx_baseline.py # the baseline's three
34 PYTHONPATH=../lib/stardesign python3 scripts/layerx_baseline.py --all \\
35 --out docs/layerx/baseline-2026-10-02.json # + supplementary
36 PYTHONPATH=../lib/stardesign python3 scripts/layerx_baseline.py --case he_pad --dt 0.02
37 PYTHONPATH=../lib/stardesign python3 scripts/layerx_baseline.py --config path/to/current.json
39``--config`` takes a YAML config, an app document (``.userdata/.../current.json``, its ``config``
40key), or a baseline JSON written by this script (its embedded ``inputs.config``), so a baseline
41can be re-burned on exactly the design it was taken on.
43Importable: ``run_case`` and ``run_baseline`` return plain dicts.
46from __future__
import annotations
57from pathlib
import Path
58from typing
import Any, Callable, Dict, Iterable, List, Optional, Tuple
60ROOT = Path(__file__).resolve().parents[1]
61if str(ROOT)
not in sys.path:
62 sys.path.insert(0, str(ROOT))
64PSI = 6894.757293168361
65DEFAULT_CONFIG = ROOT /
"configs" /
"ethalox_6800N.yaml"
74STUDY_LOCKUP_PSIA = 564.7
76CASES: Dict[str, Dict[str, Any]] = {
77 "he_pad": {
"drawing":
"copv_study_he",
"flight":
False,
78 "label":
"Helium hot-fire drawing, on the pad, erosion replay on"},
79 "he_flight": {
"drawing":
"copv_study_he",
"flight":
True,
80 "label":
"Helium hot-fire drawing, flown at the design liftoff mass"},
83 "gn2_pad": {
"drawing":
"copv_study_gn2",
"flight":
False,
"settings": {
"ack_gn2_condensation":
True},
84 "label":
"GN2 drawing (the Layer X tab's default drawing), on the pad"},
86 "gn2_flight": {
"drawing":
"copv_study_gn2",
"flight":
True,
"supplementary":
True,
87 "settings": {
"ack_gn2_condensation":
True},
88 "label":
"GN2 drawing flown at the design liftoff mass (the UI's DEFAULT_SETTINGS run)"},
92 "he_flight_he_ullage": {
"drawing":
"copv_study_he",
"flight":
True,
"supplementary":
True,
93 "config_patch": {
"lox_tank": {
"ullage_gas":
"Helium"},
94 "fuel_tank": {
"ullage_gas":
"Helium"}},
95 "label":
"Helium drawing flown, config ullage_gas set to Helium (what-if, not default)"},
97BASELINE_CASES = tuple(k
for k, v
in CASES.items()
if not v.get(
"supplementary"))
100def _patched(config: Any, patch: Optional[Dict[str, Dict[str, Any]]]) -> Any:
105 raw = config.model_dump(mode=
"json")
106 for section, fields
in patch.items():
107 raw.setdefault(section, {}).update(fields)
108 return PintleEngineConfig(**raw)
115 if path
is None or not Path(path).is_file():
118 with open(path,
"rb")
as f:
119 for chunk
in iter(
lambda: f.read(1 << 20), b
""):
125 if not path.is_dir():
128 for p
in sorted(path.glob(pattern)):
130 h.update(p.name.encode())
131 h.update(p.read_bytes())
136 """``(config, info)``. ``source``: None (LE4's YAML), a path (YAML, an app document JSON, or a
137 baseline JSON from this script), or an already-built config dict."""
142 source = DEFAULT_CONFIG
143 if isinstance(source, dict):
144 return PintleEngineConfig(**source), {
"kind":
"dict",
"path":
None}
146 if path.suffix.lower()
in (
".yaml",
".yml"):
147 return load_config(str(path)), {
"kind":
"yaml",
"path": str(path)}
148 raw = json.loads(path.read_text())
149 if isinstance(raw.get(
"inputs"), dict)
and isinstance(raw[
"inputs"].get(
"config"), dict):
150 return PintleEngineConfig(**raw[
"inputs"][
"config"]), {
"kind":
"baseline",
"path": str(path)}
151 if isinstance(raw.get(
"config"), dict):
152 return PintleEngineConfig(**raw[
"config"]), {
"kind":
"app document",
"path": str(path)}
153 raise ValueError(f
"{path}: not a YAML config, an app document or a baseline JSON")
159 found = {d.name: d
for d
in DrawingStore(
None).list()}
160 if name
not in found:
161 raise FileNotFoundError(f
"shipped drawing {name!r} not found (have: {', '.join(sorted(found))})")
168def _num(v: Any) -> Optional[float]:
171 except (TypeError, ValueError):
173 return f
if math.isfinite(f)
else None
176def _first(*values: Any) -> Optional[float]:
184def metrics(result: Dict[str, Any], prep: Any) -> Dict[str, Any]:
185 """The baseline's figures from a ``run_prepared`` result.
187 Engine quantities (impulse, thrust, Pc, Isp) are the *delivered* ones, EngineDesign's eroding
188 replay, exactly as the Layer X tab's headline reads them (LayerXResult.tsx ``figureCells``);
189 the twin's own are kept beside them. Feed quantities (burn time, tanks, bottle, O/F, stiffness)
190 are the twin's last pass, also as the tab reads them."""
191 s = result[
"summary"]
192 d = ((result.get(
"delivered")
or {}).get(
"summary"))
or {}
193 ox, fu = s[
"ox"], s[
"fuel"]
194 lockup_ui =
max(ox[
"t0_psia"], fu[
"t0_psia"])
195 target = float(prep.derived.get(
"target_lockup_psia")
or 0.0)
196 ambient_psia = float(prep.ambient_pa) / PSI
197 copv_end =
_num(s.get(
"copv_end_psia"))
198 side = s.get(
"depleted_side")
or ""
199 other = fu
if side ==
"oxidiser" else ox
if side ==
"fuel" else None
202 series = result[
"series"]
203 fire = [i
for i, f
in enumerate(series[
"firing"])
if f]
204 burn_peak = {k: (
max(series[k][
"tank_psia"][i]
for i
in fire)
if fire
else None)
for k
in (
"ox",
"fuel")}
206 out: Dict[str, Any] = {
208 "total_impulse_Ns":
_first(d.get(
"total_impulse_Ns"), s.get(
"total_impulse_Ns")),
209 "burn_time_s":
_num(s.get(
"burn_time_s")),
210 "mean_thrust_N":
_first(d.get(
"mean_thrust_N"), s.get(
"mean_thrust_N")),
211 "min_thrust_N":
_first(d.get(
"min_thrust_N"), s.get(
"min_thrust_N")),
212 "max_thrust_N":
_first(d.get(
"peak_thrust_N"), s.get(
"peak_thrust_N")),
213 "pc_mean_psia":
_first(d.get(
"pc_mean_psia"), s.get(
"pc_mean_psia")),
214 "pc_min_psia":
_first(d.get(
"pc_min_psia"), s.get(
"pc_min_psia")),
215 "pc_max_psia":
_first(d.get(
"pc_max_psia"), s.get(
"pc_max_psia")),
216 "of_mean":
_num(s.get(
"of_mean")),
217 "of_min":
_num(s.get(
"of_min")),
218 "of_max":
_num(s.get(
"of_max")),
219 "isp_mean_s":
_first(d.get(
"isp_mean_s"), s.get(
"isp_mean_s")),
220 "propellant_used_kg":
_num(s.get(
"propellant_used_kg")),
223 "total_impulse_Ns":
_num(s.get(
"total_impulse_Ns")),
224 "impulse_to_depletion_Ns":
_num(s.get(
"impulse_to_depletion_Ns")),
225 "mean_thrust_N":
_num(s.get(
"mean_thrust_N")),
226 "min_thrust_N":
_num(s.get(
"min_thrust_N")),
227 "max_thrust_N":
_num(s.get(
"peak_thrust_N")),
228 "thrust_t0_N":
_num(s.get(
"thrust_t0_N")),
229 "pc_mean_psia":
_num(s.get(
"pc_mean_psia")),
230 "isp_mean_s":
_num(s.get(
"isp_mean_s")),
233 "lockup_target_psia": target,
234 "lockup_t0_psia": lockup_ui,
235 "dome_psig":
_num(prep.derived.get(
"dome_psig")),
236 "copv_t0_psia":
_num(s.get(
"copv_t0_psia")),
237 "copv_end_psia": copv_end,
238 "copv_over_lockup_psi": (copv_end - lockup_ui)
if copv_end
is not None else None,
239 "copv_over_tank_end_psi": (copv_end -
max(ox[
"end_psia"], fu[
"end_psia"]))
if copv_end
is not None else None,
240 "copv_used_kg":
_num(s.get(
"copv_used_kg")),
241 "pressurant": prep.derived.get(
"pressurant_gas"),
244 "t0_psia": ox[
"t0_psia"],
"min_psia": ox[
"min_psia"],
"end_psia": ox[
"end_psia"],
245 "peak_psia": ox[
"peak_psia"],
"peak_burn_psia": burn_peak[
"ox"],
246 "peak_across_wall_psi": ox[
"peak_psia"] - ambient_psia,
247 "ignition_dip_psi": ox[
"ignition_dip_psi"],
"inlet_mean_psia": ox[
"inlet_mean_psia"],
248 "loaded_kg": ox[
"loaded_kg"],
"residual_kg": ox[
"residual_kg"],
251 "t0_psia": fu[
"t0_psia"],
"min_psia": fu[
"min_psia"],
"end_psia": fu[
"end_psia"],
252 "peak_psia": fu[
"peak_psia"],
"peak_burn_psia": burn_peak[
"fuel"],
253 "peak_across_wall_psi": fu[
"peak_psia"] - ambient_psia,
254 "ignition_dip_psi": fu[
"ignition_dip_psi"],
"inlet_mean_psia": fu[
"inlet_mean_psia"],
255 "loaded_kg": fu[
"loaded_kg"],
"residual_kg": fu[
"residual_kg"],
257 "tank_mawp_psi": prep.derived.get(
"tank_mawp_psi"),
259 "ox_stiffness_min":
_num(ox.get(
"stiffness_min")),
260 "fuel_stiffness_min":
_num(fu.get(
"stiffness_min")),
261 "ox_stiffness_min_ignition":
_num(ox.get(
"stiffness_min_ignition")),
262 "fuel_stiffness_min_ignition":
_num(fu.get(
"stiffness_min_ignition")),
263 "ox_dp_injector_min_psi":
_num(ox.get(
"dp_injector_min_psi")),
264 "fuel_dp_injector_min_psi":
_num(fu.get(
"dp_injector_min_psi")),
265 "stiffness_band": prep.derived.get(
"stiffness_band"),
267 "chug_margin_min":
_num(d.get(
"chug_margin_min")),
268 "chug_margin_min_t_s":
_num(d.get(
"chug_margin_min_t")),
270 "depleted_side": side,
271 "depleted_tank": s.get(
"depleted_tank"),
272 "residual_other_kg": (other[
"residual_kg"]
if other
is not None else None),
273 "residual_depleted_kg": ((ox
if side ==
"oxidiser" else fu)[
"residual_kg"]
if side
else None),
275 "throat_area_growth":
_num(d.get(
"throat_area_growth")),
276 "throat_recession_mm":
_num(d.get(
"throat_recession_mm")),
278 "converged": result.get(
"converged"),
279 "passes": len(result.get(
"passes")
or [])
or 1,
280 "steps": s.get(
"steps"),
281 "failed_steps": s.get(
"failed_steps"),
282 "extrapolated_steps": s.get(
"extrapolated_steps"),
283 "card_outside_steps": s.get(
"card_outside_steps"),
284 "t0_settled": s.get(
"t0_settled"),
285 "engine_check_worst": (result.get(
"engine_check")
or {}).get(
"worst"),
286 "replay_agreement_worst": (((result.get(
"passes")
or [{}])[-1].get(
"agreement")
or {}).get(
"worst")),
287 "cross_check": {r[
"key"]: r.get(
"rel")
for r
in ((result.get(
"cross_check")
or {}).get(
"rows")
or [])},
288 "wall_s":
_num((result.get(
"provenance")
or {}).get(
"wall_s")),
290 fl = result.get(
"flight")
292 ok = bool(fl.get(
"ok"))
295 "error": fl.get(
"error"),
296 "apogee_agl_m":
_num(fl.get(
"apogee_agl_m"))
if ok
else None,
297 "apogee_agl_ft": (
_num(fl.get(
"apogee_agl_m"))
or 0.0) / 0.3048
if ok
else None,
298 "apogee_msl_m":
_num(fl.get(
"apogee_msl_m"))
if ok
else None,
299 "apogee_time_s":
_num(fl.get(
"apogee_time_s"))
if ok
else None,
300 "max_velocity_m_s":
_num(fl.get(
"max_velocity_m_s"))
if ok
else None,
301 "max_mach":
_num(fl.get(
"max_mach"))
if ok
else None,
302 "rail_exit_velocity_m_s":
_num(fl.get(
"rail_exit_velocity_m_s"))
if ok
else None,
303 "liftoff_mass_kg":
_num(fl.get(
"liftoff_mass_kg"))
if ok
else None,
304 "burnout_mass_kg":
_num(fl.get(
"burnout_mass_kg"))
if ok
else None,
305 "liftoff_accel_g":
_num(fl.get(
"liftoff_accel_g"))
if ok
else None,
306 "max_accel_g":
_num(fl.get(
"max_accel_g"))
if ok
else None,
307 "pad": fl.get(
"pad"),
308 "in_flight": fl.get(
"in_flight"),
309 "mass_budget": fl.get(
"mass_budget"),
311 out[
"apogee_agl_m"] = (out.get(
"flight")
or {}).get(
"apogee_agl_m")
320 r = subprocess.run([
"git",
"describe",
"--always",
"--dirty"], cwd=ROOT, capture_output=
True, text=
True, timeout=5)
321 return r.stdout.strip()
or "unknown"
322 except (OSError, subprocess.SubprocessError):
327 out: Dict[str, Any] = {
"python": platform.python_version(),
"platform": platform.platform()}
328 for mod
in (
"numpy",
"scipy",
"CoolProp",
"fluids",
"rocketpy",
"numba",
"feedtwin"):
331 out[mod] = getattr(m,
"__version__",
"?")
337def run_case(name: str, *, config: Any =
None, config_source: Any =
None, dt: Optional[float] =
None,
338 progress: Optional[Callable[[str, float],
None]] =
None, keep_result: bool =
False) -> Dict[str, Any]:
339 """Burn one case and return ``{"case", "settings", "inputs", "metrics", ...}``.
341 ``config``: an engine config to use (it is deep-copied, as the router does); otherwise
342 ``config_source`` is loaded with :func:`load_engine_config`. ``dt`` overrides the default step."""
344 from engine.layerx import LayerXSettings, prepare, run_prepared
349 info: Dict[str, Any] = {
"kind":
"given",
"path":
None}
353 kwargs: Dict[str, Any] = {
"drawing_id": drawing.id,
"flight": bool(spec[
"flight"]),
354 "tank_pressure_psia": STUDY_LOCKUP_PSIA, **(spec.get(
"settings")
or {})}
356 kwargs[
"dt"] = float(dt)
357 settings = LayerXSettings(**kwargs)
359 started = time.perf_counter()
360 cfg =
_patched(copy.deepcopy(config), spec.get(
"config_patch"))
361 runner = PintleEngineRunner(cfg)
362 prep = prepare(cfg, runner, drawing, settings, [])
363 prep_s = time.perf_counter() - started
364 failing = [f
"{c.key}: {c.detail}" for c
in prep.checks
if c.status ==
"fail"]
366 raise RuntimeError(f
"{name}: preflight failed: {failing}")
367 result = run_prepared(prep, runner=runner, replay=prep.settings.replay, config=cfg,
368 progress=progress
or (
lambda stage, frac:
None))
369 total_s = time.perf_counter() - started
371 m[
"wall_total_s"] = total_s
372 m[
"wall_prepare_s"] = prep_s
373 cea = cea_table_path(cfg)
376 "label": spec[
"label"],
377 "supplementary": bool(spec.get(
"supplementary")),
378 "config_patch": spec.get(
"config_patch"),
379 "settings": {k: v
for k, v
in settings.__dict__.items()},
381 "config_source": info,
382 "config_sha256": config_fingerprint(config),
383 "config_sha256_burned": config_fingerprint(cfg),
384 "drawing": {
"name": drawing.name,
"id": drawing.id,
"sha256": drawing.sha256,
"source": drawing.source},
385 "cea_table": {
"name": cea.name
if cea
else None,
"sha256":
_sha256_file(cea)},
386 "state_machines_sha256":
_sha256_dir(machines_dir()),
387 "ambient_pa": prep.ambient_pa,
389 "checks": [{
"key": c.key,
"status": c.status,
"detail": c.detail}
for c
in prep.checks
390 if c.status
in (
"warn",
"fail")],
394 out[
"result"] = result
398def run_baseline(cases: Optional[Iterable[str]] =
None, *, config_source: Any =
None, dt: Optional[float] =
None,
399 embed_config: bool =
True, verbose: bool =
True) -> Dict[str, Any]:
400 """Every case in ``cases`` (all by default) on one config. The config is embedded so the
401 baseline can be burned again on exactly this design (``--config <baseline.json>``)."""
402 from dataclasses
import asdict
408 names = list(cases
or BASELINE_CASES)
409 out: Dict[str, Any] = {
410 "what":
"LE4 Layer X baseline: in-process burns at today's default LayerXSettings",
411 "created": time.strftime(
"%Y-%m-%dT%H:%M:%S%z"),
414 "config_source": info,
415 "config_sha256": config_fingerprint(config),
417 "layerx_settings_defaults": {k: v
for k, v
in asdict(LayerXSettings(drawing_id=
"")).items()
418 if k !=
"drawing_id"},
423 print(f
"[layerx_baseline] {name}: {CASES[name]['label']} ...", flush=
True)
424 out[
"cases"][name] =
run_case(name, config=config, dt=dt)
425 out[
"cases"][name][
"inputs"][
"config_source"] = info
429 out[
"inputs"] = {
"config": config.model_dump(mode=
"json")}
436def _f(v: Any, nd: int = 1, unit: str =
"") -> str:
438 return "-" if f
is None else f
"{f:,.{nd}f}{(' ' + unit) if unit else ''}"
443 o, fu = m[
"ox_tank"], m[
"fuel_tank"]
445 f
" {case['case']} ({case['inputs']['drawing']['name']}, {m['pressurant']}, dt {case['settings']['dt']} s)",
446 f
" impulse {_f(m['total_impulse_Ns'], 0, 'N·s')} burn {_f(m['burn_time_s'], 3, 's')} "
447 f
"thrust mean/min/max {_f(m['mean_thrust_N'], 0)}/{_f(m['min_thrust_N'], 0)}/{_f(m['max_thrust_N'], 0)} N",
448 f
" Pc mean {_f(m['pc_mean_psia'], 1, 'psia')} ({_f(m['pc_min_psia'], 1)}-{_f(m['pc_max_psia'], 1)}) "
449 f
"O/F {_f(m['of_mean'], 4)} Isp {_f(m['isp_mean_s'], 2, 's')} burned {_f(m['propellant_used_kg'], 3, 'kg')}",
450 f
" bottle {_f(m['copv_t0_psia'], 0)} -> {_f(m['copv_end_psia'], 1, 'psia')} at burnout, "
451 f
"{_f(m['copv_over_lockup_psi'], 1, 'psi')} over lockup ({_f(m['copv_over_tank_end_psi'], 1, 'psi')} over the tanks at end), "
452 f
"{_f(m['copv_used_kg'], 4, 'kg')} used",
453 f
" tanks LOX t0/min/end/peak {_f(o['t0_psia'])}/{_f(o['min_psia'])}/{_f(o['end_psia'])}/{_f(o['peak_psia'])} psia "
454 f
"fuel {_f(fu['t0_psia'])}/{_f(fu['min_psia'])}/{_f(fu['end_psia'])}/{_f(fu['peak_psia'])} psia",
455 f
" min dP/Pc LOX {_f((m['ox_stiffness_min'] or 0) * 100, 2, '%')} (ign {_f((m['ox_stiffness_min_ignition'] or 0) * 100, 2, '%')}) "
456 f
"fuel {_f((m['fuel_stiffness_min'] or 0) * 100, 2, '%')} (ign {_f((m['fuel_stiffness_min_ignition'] or 0) * 100, 2, '%')})",
457 f
" chug margin min {_f(m['chug_margin_min'], 3)} at {_f(m['chug_margin_min_t_s'], 2, 's')} "
458 f
"depleted {m['depleted_side']} ({_f(m['residual_other_kg'], 4, 'kg')} of the other left) "
459 f
"throat +{_f((m['throat_area_growth'] or 0) * 100, 2, '%')} area, {_f(m['throat_recession_mm'], 3, 'mm')}",
460 f
" converged {m['converged']} in {m['passes']} pass(es), failed steps {m['failed_steps']}, "
461 f
"wall {_f(m['wall_total_s'], 1, 's')} (prepare {_f(m['wall_prepare_s'], 1, 's')})",
466 lines.append(f
" apogee {_f(fl['apogee_agl_m'], 0, 'm')} AGL ({_f(fl['apogee_agl_ft'], 0, 'ft')}), "
467 f
"liftoff {_f(fl['liftoff_mass_kg'], 2, 'kg')} at {_f(fl['liftoff_accel_g'], 2, 'g')}, "
468 f
"max {_f(fl['max_accel_g'], 2, 'g')}, Mach {_f(fl['max_mach'], 2)}")
470 lines.append(f
" flight FAILED: {fl.get('error')}")
471 return "\n".join(lines)
475 if hasattr(o,
"item"):
480def main(argv: Optional[List[str]] =
None) -> int:
481 ap = argparse.ArgumentParser(description=__doc__.split(
"\n\n")[0])
482 ap.add_argument(
"--case", action=
"append", choices=sorted(CASES),
483 help=f
"case to run (repeatable; default the baseline's {', '.join(BASELINE_CASES)})")
484 ap.add_argument(
"--all", action=
"store_true", help=
"the baseline's cases and the supplementary ones")
485 ap.add_argument(
"--dt", type=float, default=
None, help=
"time step [s] (default: LayerXSettings's, 0.05)")
486 ap.add_argument(
"--out", type=Path, default=
None, help=
"write the JSON here")
487 ap.add_argument(
"--config", default=
None,
488 help=
"engine config: YAML, app document JSON, or a baseline JSON (default configs/ethalox_6800N.yaml)")
489 ap.add_argument(
"--no-embed", action=
"store_true", help=
"do not embed the config in the JSON")
490 args = ap.parse_args(argv)
492 cases = list(CASES)
if args.all
else args.case
493 data =
run_baseline(cases, config_source=args.config, dt=args.dt, embed_config=
not args.no_embed)
495 args.out.parent.mkdir(parents=
True, exist_ok=
True)
496 args.out.write_text(json.dumps(data, indent=1, default=_json_default) +
"\n")
497 print(f
"[layerx_baseline] wrote {args.out}")
501if __name__ ==
"__main__":
502 raise SystemExit(
main())
Dict[str, Any] metrics(Dict[str, Any] result, Any prep)
Dict[str, Any] run_baseline(Optional[Iterable[str]] cases=None, *, Any config_source=None, Optional[float] dt=None, bool embed_config=True, bool verbose=True)
Tuple[Any, Dict[str, Any]] load_engine_config(Any source=None)
Optional[str] _sha256_file(Optional[Path] path)
str format_case(Dict[str, Any] case)
Any find_drawing(str name)
Dict[str, Any] run_case(str name, *, Any config=None, Any config_source=None, Optional[float] dt=None, Optional[Callable[[str, float], None]] progress=None, bool keep_result=False)
Any _patched(Any config, Optional[Dict[str, Dict[str, Any]]] patch)
Optional[float] _first(*Any values)
Dict[str, Any] _versions()
Optional[str] _sha256_dir(Path path, str pattern="*")
Optional[float] _num(Any v)
str _f(Any v, int nd=1, str unit="")