2424REPO_ROOT = HERE .parent .parent
2525EXAMPLES_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
465472def 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
476479def 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
488486def 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
498493def 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