Skip to content

Commit 81d561d

Browse files
sbryngelsonclaude
andcommitted
Auto-generate description labels and extend DEPENDENCIES schema
- Auto-append value_labels to descriptions in _r(), removing hardcoded label parentheticals from _SIMPLE_DESCS (7 entries) and DESCRIPTIONS (10 entries). Descriptions now stay in sync with CONSTRAINTS. - Add requires_value and when_value to DEPENDENCIES schema with full validation, error formatting, and dependency checking support. - Add DEPENDENCIES entries for bubbles_euler (requires model_eqns, riemann_solver, avg_state values) and model_eqns (when_value for models 2, 3, 4). - Extend docs_gen.py and gen_case_constraints_docs.py to render new schema keys with human-readable labels from get_value_label(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 545f3a9 commit 81d561d

6 files changed

Lines changed: 487 additions & 166 deletions

File tree

toolchain/mfc/gen_case_constraints_docs.py

Lines changed: 176 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
REPO_ROOT = HERE.parent.parent
2525
EXAMPLES_DIR = REPO_ROOT / "examples"
2626

27+
# Make the params package importable
28+
_toolchain_dir = str(HERE.parent)
29+
if _toolchain_dir not in sys.path:
30+
sys.path.insert(0, _toolchain_dir)
31+
32+
from mfc.params import REGISTRY, CONSTRAINTS, DEPENDENCIES, get_value_label # noqa: E402
33+
2734

2835
# ---------------------------------------------------------------------------
2936
# Data structures
@@ -463,36 +470,24 @@ def summarize_case_params(params: Dict[str, Any]) -> Dict[str, Any]:
463470

464471

465472
def get_model_name(model_eqns: int | None) -> str:
466-
"""Get human-friendly model name"""
467-
models = {
468-
1: "π-γ (Compressible Euler)",
469-
2: "5-Equation (Multiphase)",
470-
3: "6-Equation (Phase Change)",
471-
4: "4-Equation (Single Component)"
472-
}
473-
return models.get(model_eqns, "Not specified")
473+
"""Get human-friendly model name from schema."""
474+
if model_eqns is None:
475+
return "Not specified"
476+
return get_value_label("model_eqns", model_eqns) or "Not specified"
474477

475478

476479
def get_riemann_solver_name(solver: int | None) -> str:
477-
"""Get Riemann solver name"""
478-
solvers = {
479-
1: "HLL",
480-
2: "HLLC",
481-
3: "Exact",
482-
4: "HLLD",
483-
5: "Lax-Friedrichs"
484-
}
485-
return solvers.get(solver, "Not specified")
480+
"""Get Riemann solver name from schema."""
481+
if solver is None:
482+
return "Not specified"
483+
return get_value_label("riemann_solver", solver) or "Not specified"
486484

487485

488486
def get_time_stepper_name(stepper: int | None) -> str:
489-
"""Get time stepper name"""
490-
steppers = {
491-
1: "RK1 (Forward Euler)",
492-
2: "RK2",
493-
3: "RK3 (SSP)"
494-
}
495-
return steppers.get(stepper, "Not specified")
487+
"""Get time stepper name from schema."""
488+
if stepper is None:
489+
return "Not specified"
490+
return get_value_label("time_stepper", stepper) or "Not specified"
496491

497492

498493
def render_playbook_card(entry: PlaybookEntry, summary: Dict[str, Any]) -> str: # pylint: disable=too-many-branches,too-many-statements
@@ -778,67 +773,120 @@ def render_markdown(rules: Iterable[Rule]) -> str: # pylint: disable=too-many-l
778773

779774
lines.append("")
780775

781-
# 3. Model Equations
776+
# 3. Model Equations (data-driven from schema)
782777
lines.append("## 🔢 Model Equations\n")
783778
lines.append("Choose your governing equations:\n")
784779
lines.append("")
785780

786-
lines.append("<details>")
787-
lines.append("<summary><b>Model 1: π-γ (Compressible Euler)</b></summary>\n")
788-
lines.append("- **Use for:** Single-fluid compressible flow")
789-
lines.append("- **Value:** `model_eqns = 1`")
790-
lines.append("- **Note:** Cannot use `num_fluids`, bubbles, or certain WENO variants")
791-
lines.append("</details>\n")
781+
def _format_model_requirements(val: int) -> str:
782+
"""Auto-generate requirements string from DEPENDENCIES['model_eqns']['when_value']."""
783+
me_dep = DEPENDENCIES.get("model_eqns", {})
784+
wv = me_dep.get("when_value", {}).get(val, {})
785+
if not wv:
786+
return ""
787+
parts = []
788+
if "requires" in wv:
789+
parts.extend(f"Set `{r}`" for r in wv["requires"])
790+
if "requires_value" in wv:
791+
for rv_param, rv_vals in wv["requires_value"].items():
792+
labeled = [f"`{v}` ({get_value_label(rv_param, v)})" for v in rv_vals]
793+
parts.append(f"`{rv_param}` = {' or '.join(labeled)}")
794+
return ", ".join(parts)
795+
796+
# Curated editorial notes keyed by model_eqns value
797+
_model_notes = {
798+
1: {
799+
"use_for": "Single-fluid compressible flow",
800+
"note": "Cannot use `num_fluids`, bubbles, or certain WENO variants",
801+
},
802+
2: {
803+
"use_for": "Multiphase, bubbles, elastic materials, MHD",
804+
"note": "Compatible with most physics models",
805+
},
806+
3: {
807+
"use_for": "Phase change, cavitation",
808+
"note": "Not compatible with bubbles or 3D cylindrical",
809+
},
810+
4: {
811+
"use_for": "Single-component flows with bubbles",
812+
},
813+
}
792814

793-
lines.append("<details>")
794-
lines.append("<summary><b>Model 2: 5-Equation (Most versatile)</b></summary>\n")
795-
lines.append("- **Use for:** Multiphase, bubbles, elastic materials, MHD")
796-
lines.append("- **Value:** `model_eqns = 2`")
797-
lines.append("- **Requirements:** Set `num_fluids`")
798-
lines.append("- **Compatible with:** Most physics models")
799-
lines.append("</details>\n")
815+
# Auto-populate requirements from schema
816+
for _val in _model_notes:
817+
_auto_reqs = _format_model_requirements(_val)
818+
if _auto_reqs:
819+
_model_notes[_val]["requirements"] = _auto_reqs
800820

801-
lines.append("<details>")
802-
lines.append("<summary><b>Model 3: 6-Equation (Phase change)</b></summary>\n")
803-
lines.append("- **Use for:** Phase change, cavitation")
804-
lines.append("- **Value:** `model_eqns = 3`")
805-
lines.append("- **Requirements:** `riemann_solver = 2` (HLLC), `avg_state = 2`, `wave_speeds = 1`")
806-
lines.append("- **Note:** Not compatible with bubbles or 3D cylindrical")
807-
lines.append("</details>\n")
821+
model_constraint = CONSTRAINTS["model_eqns"]
822+
for val in model_constraint["choices"]:
823+
label = get_value_label("model_eqns", val)
824+
notes = _model_notes.get(val, {})
825+
lines.append("<details>")
826+
lines.append(f"<summary><b>Model {val}: {label}</b></summary>\n")
827+
if "use_for" in notes:
828+
lines.append(f"- **Use for:** {notes['use_for']}")
829+
lines.append(f"- **Value:** `model_eqns = {val}`")
830+
if "requirements" in notes:
831+
lines.append(f"- **Requirements:** {notes['requirements']}")
832+
if "note" in notes:
833+
lines.append(f"- **Note:** {notes['note']}")
834+
lines.append("</details>\n")
808835

809-
lines.append("<details>")
810-
lines.append("<summary><b>Model 4: 4-Equation (Single component)</b></summary>\n")
811-
lines.append("- **Use for:** Single-component flows with bubbles")
812-
lines.append("- **Value:** `model_eqns = 4`")
813-
lines.append("- **Requirements:** `num_fluids = 1`, set `rhoref` and `pref`")
814-
lines.append("</details>\n")
836+
# 4. Riemann Solvers (data-driven from schema)
837+
# Curated editorial notes keyed by riemann_solver value
838+
_solver_notes = {
839+
1: {"best_for": "MHD, elastic materials", "requirements": "—"},
840+
2: {"best_for": "Bubbles, phase change, multiphase", "requirements": "`avg_state=2` for bubbles"},
841+
3: {"best_for": "High accuracy (expensive)", "requirements": "—"},
842+
4: {"best_for": "MHD (advanced)", "requirements": "MHD only, no relativity"},
843+
5: {"best_for": "Robust fallback", "requirements": "Not with cylindrical+viscous"},
844+
}
815845

816-
# 4. Riemann Solvers (simplified)
817846
lines.append("## ⚙️ Riemann Solvers\n")
818847
lines.append("| Solver | `riemann_solver` | Best For | Requirements |")
819848
lines.append("|--------|-----------------|----------|-------------|")
820-
lines.append("| **HLL** | `1` | MHD, elastic materials | — |")
821-
lines.append("| **HLLC** | `2` | Bubbles, phase change, multiphase | `avg_state=2` for bubbles |")
822-
lines.append("| **Exact** | `3` | High accuracy (expensive) | — |")
823-
lines.append("| **HLLD** | `4` | MHD (advanced) | MHD only, no relativity |")
824-
lines.append("| **Lax-Friedrichs** | `5` | Robust fallback | Not with cylindrical+viscous |")
849+
850+
solver_constraint = CONSTRAINTS["riemann_solver"]
851+
for val in solver_constraint["choices"]:
852+
label = get_value_label("riemann_solver", val)
853+
notes = _solver_notes.get(val, {})
854+
best = notes.get("best_for", "—")
855+
reqs = notes.get("requirements", "—")
856+
lines.append(f"| **{label}** | `{val}` | {best} | {reqs} |")
857+
825858
lines.append("")
826859

827-
# 5. Bubble Models (enhanced with collapsible)
860+
# 5. Bubble Models (data-driven from schema dependencies + curated notes)
828861
if "bubbles_euler" in by_param or "bubbles_lagrange" in by_param:
829862
lines.append("## 💧 Bubble Models\n")
830863
lines.append("")
831864

865+
# Euler-Euler: inject schema dependency info (data-driven)
832866
lines.append("<details>")
833867
lines.append("<summary><b>Euler-Euler (`bubbles_euler`)</b></summary>\n")
834868
lines.append("**Requirements:**")
835-
lines.append("- `model_eqns = 2` or `4`")
836-
lines.append("- `riemann_solver = 2` (HLLC)")
837-
lines.append("- `avg_state = 2`")
838-
lines.append("- Set `nb` (number of bins) ≥ 1\n")
869+
be_dep = DEPENDENCIES.get("bubbles_euler", {})
870+
be_when_true = be_dep.get("when_true", {})
871+
be_rv = be_when_true.get("requires_value", {})
872+
for rv_param, rv_vals in be_rv.items():
873+
labeled = [f"`{v}` ({get_value_label(rv_param, v)})" for v in rv_vals]
874+
lines.append(f"- `{rv_param}` = {' or '.join(labeled)}")
875+
be_recs = be_when_true.get("recommends", [])
876+
if be_recs:
877+
lines.append(f"- Recommended to also set: {', '.join(f'`{r}`' for r in be_recs)}")
878+
lines.append("")
839879
lines.append("**Extensions:**")
840-
lines.append("- `polydisperse = T`: Multiple bubble sizes (requires odd `nb > 1`)")
841-
lines.append("- `qbmm = T`: Quadrature method (requires `nnode = 4`)")
880+
# Inject polydisperse dependency
881+
pd_dep = DEPENDENCIES.get("polydisperse", {})
882+
pd_reqs = pd_dep.get("when_true", {}).get("requires", [])
883+
pd_req_str = f" (requires {', '.join(f'`{r}`' for r in pd_reqs)})" if pd_reqs else ""
884+
lines.append(f"- `polydisperse = T`: Multiple bubble sizes{pd_req_str}, odd `nb > 1`")
885+
# Inject qbmm dependency
886+
qb_dep = DEPENDENCIES.get("qbmm", {})
887+
qb_recs = qb_dep.get("when_true", {}).get("recommends", [])
888+
qb_rec_str = f" (recommends {', '.join(f'`{r}`' for r in qb_recs)})" if qb_recs else ""
889+
lines.append(f"- `qbmm = T`: Quadrature method{qb_rec_str}, requires `nnode = 4`")
842890
lines.append("- `adv_n = T`: Number density advection (requires `num_fluids = 1`)")
843891
lines.append("</details>\n")
844892

@@ -851,37 +899,30 @@ def render_markdown(rules: Iterable[Rule]) -> str: # pylint: disable=too-many-l
851899
lines.append("**Note:** Tracks individual bubbles")
852900
lines.append("</details>\n")
853901

854-
# 6. Condensed Parameter Reference
902+
# 6. Condensed Parameter Reference (auto-collected from schema)
855903
lines.append("## 📖 Quick Parameter Reference\n")
856904
lines.append("Key parameters and their constraints:\n")
857905

858-
# Highlight only the most important parameters in collapsible sections
859-
important_params = {
860-
"MHD": "mhd",
861-
"Surface Tension": "surface_tension",
862-
"Viscosity": "viscous",
863-
"Number of Fluids": "num_fluids",
864-
"Cylindrical Coordinates": "cyl_coord",
865-
"Immersed Boundaries": "ib",
866-
}
906+
# Auto-collect all params that have CONSTRAINTS or DEPENDENCIES entries
907+
quick_ref_params = sorted(set(CONSTRAINTS.keys()) | set(DEPENDENCIES.keys()))
867908

868-
for title, param in important_params.items():
869-
if param not in by_param:
870-
continue
909+
for param in quick_ref_params:
910+
title = feature_title(param)
871911

872-
rules_for_param = by_param[param]
912+
# Gather schema info
913+
constraint = CONSTRAINTS.get(param, {})
914+
dep = DEPENDENCIES.get(param, {})
873915

874-
# Get key info
916+
# Gather AST-extracted rules
917+
rules_for_param = by_param.get(param, [])
875918
requirements = []
876919
incompatibilities = []
877920
ranges = []
878921

879922
for rule in rules_for_param:
880923
msg = rule.message
881-
# Skip IGR-related messages
882924
if "IGR" in msg:
883925
continue
884-
885926
kind = classify_message(msg)
886927
if kind == "requirement":
887928
requirements.append(msg)
@@ -890,12 +931,65 @@ def render_markdown(rules: Iterable[Rule]) -> str: # pylint: disable=too-many-l
890931
elif kind == "range":
891932
ranges.append(msg)
892933

893-
if not (requirements or incompatibilities or ranges):
934+
# Build schema constraint summary
935+
schema_parts = []
936+
if "choices" in constraint:
937+
labels = constraint.get("value_labels", {})
938+
if labels:
939+
items = [f"`{v}` = {labels[v]}" for v in constraint["choices"] if v in labels]
940+
schema_parts.append("Choices: " + ", ".join(items))
941+
else:
942+
schema_parts.append(f"Choices: {constraint['choices']}")
943+
if "min" in constraint:
944+
schema_parts.append(f"Min: {constraint['min']}")
945+
if "max" in constraint:
946+
schema_parts.append(f"Max: {constraint['max']}")
947+
948+
# Build dependency summary
949+
dep_parts = []
950+
951+
def _render_cond_parts(trigger_str, cond_dict):
952+
"""Render a condition dict into dep_parts entries."""
953+
if "requires" in cond_dict:
954+
dep_parts.append(f"When {trigger_str}, requires: {', '.join(f'`{r}`' for r in cond_dict['requires'])}")
955+
if "requires_value" in cond_dict:
956+
rv_items = []
957+
for rv_p, rv_vs in cond_dict["requires_value"].items():
958+
labeled = [f"`{v}` ({get_value_label(rv_p, v)})" for v in rv_vs]
959+
rv_items.append(f"`{rv_p}` = {' or '.join(labeled)}")
960+
dep_parts.append(f"When {trigger_str}, requires {', '.join(rv_items)}")
961+
if "recommends" in cond_dict:
962+
dep_parts.append(f"When {trigger_str}, recommends: {', '.join(f'`{r}`' for r in cond_dict['recommends'])}")
963+
964+
for cond_key in ["when_true", "when_set"]:
965+
cond = dep.get(cond_key, {})
966+
if cond:
967+
trigger = "enabled" if cond_key == "when_true" else "set"
968+
_render_cond_parts(trigger, cond)
969+
970+
if "when_value" in dep:
971+
for wv_val, wv_cond in dep["when_value"].items():
972+
_render_cond_parts(f"= {wv_val}", wv_cond)
973+
974+
# Skip if nothing to show
975+
if not (schema_parts or dep_parts or requirements or incompatibilities or ranges):
894976
continue
895977

896978
lines.append(f"\n<details>")
897979
lines.append(f"<summary><b>{title}</b> (`{param}`)</summary>\n")
898980

981+
if schema_parts:
982+
lines.append("**Schema constraints:**")
983+
for sp in schema_parts:
984+
lines.append(f"- {sp}")
985+
lines.append("")
986+
987+
if dep_parts:
988+
lines.append("**Dependencies:**")
989+
for dp in dep_parts:
990+
lines.append(f"- {dp}")
991+
lines.append("")
992+
899993
if requirements:
900994
lines.append("**Requirements:**")
901995
for req in requirements[:3]:

0 commit comments

Comments
 (0)