Skip to content

Commit 5bec893

Browse files
sbryngelsonclaude
andcommitted
Add math symbols to parameter registry, cross-ref validation, and doc fixes
- Add math_symbol field to ParamDef for LaTeX symbol mapping (e.g. gamma -> γ_k) - Define ~70 symbols inline on _r() calls in definitions.py (single source of truth) - Show Symbol column in auto-generated parameters.md for physics parameters - Extend lint_docs.py to validate param refs in case.md (518 refs, was unchecked) - Add @ref cross-link validation between doc pages - Fix doc bugs: radiue->radius, t_step_end->t_step_stop, remove stale tau_wrt row - Add cross-page @ref links between equations.md, case.md, and parameters.md - Update contributing guide Step 1 to document math= and desc= kwargs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1f604d2 commit 5bec893

8 files changed

Lines changed: 267 additions & 89 deletions

File tree

docs/documentation/case.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,9 @@ Details of implementation of viscosity in MFC can be found in \cite Coralic15.
388388
- `fluid_pp(i)%%G` is required for `hypoelasticity`.
389389

390390
### 6. Simulation Algorithm
391-
391+
392+
See @ref equations "Equations" for the mathematical models these parameters control.
393+
392394
| Parameter | Type | Description |
393395
| ---: | :----: | :--- |
394396
| `bc_[x,y,z]%%beg[end]` | Integer | Beginning [ending] boundary condition in the $[x,y,z]$-direction (negative integer, see table [Boundary Conditions](#boundary-conditions)) |
@@ -545,7 +547,7 @@ This option requires `weno_Re_flux` to be true because cell boundary values are
545547
| `type`* | Integer | The geometry of the patch. [1]: Line [2]: Circle [3]: Rectangle |
546548
| `x[y,z]_centroid`* | Real | Centroid of the boundary patch in the x[y,z]-direction |
547549
| `length_x[y,z]`* | Real | Length of the boundary patch in the x[y,z]-direction |
548-
| `radiue`* | Real | Radius of the boundary patch |
550+
| `radius`* | Real | Radius of the boundary patch |
549551
*: These parameters should be prepended with `patch_bc(j)%` where $j$ is the patch index.
550552

551553
Boundary condition patches can be used with the following boundary condition types:
@@ -563,7 +565,7 @@ Squares and circles on each face are supported for 3D simulations.
563565
- `dt` specifies the constant time step size used in the simulation.
564566
The value of `dt` needs to be sufficiently small to satisfy the Courant-Friedrichs-Lewy (CFL) condition.
565567

566-
- `t_step_start` and `t_step_end` define the time steps at which the simulation starts and ends.
568+
- `t_step_start` and `t_step_stop` define the time steps at which the simulation starts and ends.
567569

568570
`t_step_save` is the time step interval for data output during simulation.
569571
To newly start the simulation, set `t_step_start = 0`.
@@ -612,7 +614,6 @@ To restart the simulation from $k$-th time step, see @ref running "Restarting Ca
612614
| `schlieren_wrt` | Logical | Add the numerical schlieren to the database|
613615
| `qm_wrt` | Logical | Add the Q-criterion to the database|
614616
| `liutex_wrt` | Logical | Add the Liutex to the database|
615-
| `tau_wrt` | Logical | Add the elastic stress components to the database|
616617
| `fd_order` | Integer | Order of finite differences for computing the vorticity and the numerical Schlieren function [1,2,4] |
617618
| `schlieren_alpha(i)` | Real | Intensity of the numerical Schlieren computed via `alpha(i)` |
618619
| `probe_wrt` | Logical | Write the flow chosen probes data files for each time step |
@@ -797,6 +798,8 @@ This table lists the sub-grid bubble model parameters, which can be utilized in
797798

798799
Implementation of the parameters into the model follows \cite Ando10.
799800

801+
See @ref equations "Equations" Section 9 for the bubble dynamics equations.
802+
800803
#### 9.1 Ensemble-Averaged Bubble Model
801804

802805
| Parameter | Type | Description |

docs/documentation/contributing.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,27 @@ Adding a parameter touches both the Python toolchain and Fortran source. Follow
212212
Add a call to `_r()` inside the `_load()` function:
213213

214214
```python
215-
_r("my_param", REAL, {"my_feature_tag"})
215+
_r("my_param", REAL, {"my_feature_tag"},
216+
desc="Description of the parameter",
217+
math=r"\f$\xi\f$")
216218
```
217219

218-
The arguments are: name, type (`INT`, `REAL`, `LOG`, `STR`), and a set of feature tags. You can add an explicit description with `desc="..."`, otherwise one is auto-generated from `_SIMPLE_DESCS` or `_ATTR_DESCS`.
220+
The arguments are:
221+
- **name**: parameter name (must match the Fortran namelist variable)
222+
- **type**: `INT`, `REAL`, `LOG`, `STR`, or `A_REAL` (analytic expression)
223+
- **tags**: set of feature tags for grouping (e.g. `{"bubbles"}`, `{"mhd"}`)
224+
- **desc**: human-readable description (optional; auto-generated from `_SIMPLE_DESCS` or `_ATTR_DESCS` if omitted)
225+
- **math**: LaTeX math symbol in Doxygen format (optional; shown in the Symbol column of @ref parameters)
226+
227+
For indexed families like `fluid_pp`, put the symbol next to its attribute name using tuples:
228+
229+
```python
230+
for f in range(1, NF + 1):
231+
px = f"fluid_pp({f})%"
232+
for a, sym in [("gamma", r"\f$\gamma_k\f$"),
233+
("my_attr", r"\f$\xi_k\f$")]: # <-- add here
234+
_r(f"{px}{a}", REAL, math=sym)
235+
```
219236

220237
**Step 2: Add constraints** (same file, `CONSTRAINTS` dict)
221238

docs/documentation/equations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Each section notes the input parameter(s) that activate the corresponding physic
77

88
The models and algorithms described here are detailed in \cite Wilfong26 (MFC 5.0) and \cite Bryngelson21. Foundational references for each model are cited inline; see the \ref citelist "Bibliography" for full details.
99

10+
For parameter details and allowed values, see @ref case "Case Files" and the @ref parameters "Case Parameters" reference.
11+
1012
---
1113

1214
## 1. Overview

toolchain/mfc/lint_docs.py

Lines changed: 124 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Check that file paths, cite keys, and parameters in docs still exist."""
1+
"""Check that file paths, cite keys, parameters, and @ref targets in docs still exist."""
22

33
import re
44
import sys
@@ -34,6 +34,28 @@
3434
r"|^[A-Z]" # constants/types (uppercase start)
3535
)
3636

37+
# Backtick tokens in case.md that are not real parameters (analytical shorthands,
38+
# stress tensor component names, prose identifiers, hardcoded constants)
39+
CASE_MD_SKIP = {
40+
# Analytical shorthand variables (stretching formulas, "Analytical Definition" table)
41+
"eps", "lx", "ly", "lz", "xc", "yc", "zc", "x_cb",
42+
# Stress tensor component names (descriptive, not params)
43+
"tau_xx", "tau_xy", "tau_xz", "tau_yy", "tau_yz", "tau_zz",
44+
# Prose identifiers (example names, math symbols)
45+
"scaling", "c_h", "thickness",
46+
# Hardcoded Fortran constants (not case-file params)
47+
"init_dir", "zeros_default",
48+
}
49+
50+
# Docs to check for parameter references, with per-file skip sets
51+
PARAM_DOCS = {
52+
"docs/documentation/equations.md": set(),
53+
"docs/documentation/case.md": CASE_MD_SKIP,
54+
}
55+
56+
# Match @ref page_id patterns
57+
REF_RE = re.compile(r"@ref\s+(\w+)")
58+
3759

3860
def check_docs(repo_root: Path) -> list[str]:
3961
"""Check that file paths referenced in documentation still exist."""
@@ -84,12 +106,45 @@ def check_cite_keys(repo_root: Path) -> list[str]:
84106
return errors
85107

86108

87-
def check_param_refs(repo_root: Path) -> list[str]:
88-
"""Check that parameter names in equations.md exist in the MFC registry."""
89-
eq_path = repo_root / "docs" / "documentation" / "equations.md"
90-
if not eq_path.exists():
91-
return []
109+
def _strip_code_blocks(text: str) -> str:
110+
"""Remove fenced code blocks (``` ... ```) from markdown text."""
111+
lines = text.split("\n")
112+
result = []
113+
in_block = False
114+
for line in lines:
115+
if line.strip().startswith("```"):
116+
in_block = not in_block
117+
continue
118+
if not in_block:
119+
result.append(line)
120+
return "\n".join(result)
121+
122+
123+
def _is_valid_param(param: str, valid_params: set, sub_params: set) -> bool:
124+
"""Check if a param name (possibly with %) is valid against REGISTRY."""
125+
if "(" in param or ")" in param:
126+
return True # Skip indexed refs like patch_icpp(i)%vel(j)
127+
128+
base = param.split("%")[0] if "%" in param else param
129+
130+
if base in valid_params or base in sub_params:
131+
return True
132+
133+
# Check sub-param part after %
134+
if "%" in param:
135+
sub = param.split("%")[-1]
136+
if sub in sub_params:
137+
return True
138+
139+
# Family prefix check
140+
if any(p.startswith(base) for p in valid_params):
141+
return True
142+
143+
return False
92144

145+
146+
def check_param_refs(repo_root: Path) -> list[str]: # pylint: disable=too-many-locals
147+
"""Check that parameter names in documentation exist in the MFC registry."""
93148
# Import REGISTRY from the toolchain
94149
toolchain_dir = str(repo_root / "toolchain")
95150
if toolchain_dir not in sys.path:
@@ -101,36 +156,73 @@ def check_param_refs(repo_root: Path) -> list[str]:
101156
return []
102157

103158
valid_params = set(REGISTRY.all_params.keys())
104-
# Build set of sub-parameter suffixes (the part after %)
105-
sub_params = {p.split("%")[-1] for p in valid_params if "%" in p}
106-
text = eq_path.read_text(encoding="utf-8")
159+
# Build set of sub-parameter base names (strip trailing (N) indexes)
160+
_sub_raw = {p.split("%")[-1] for p in valid_params if "%" in p}
161+
sub_params = set()
162+
for s in _sub_raw:
163+
sub_params.add(s)
164+
base = re.sub(r"\(\d+(?:,\s*\d+)*\)$", "", s)
165+
if base != s:
166+
sub_params.add(base)
167+
107168
errors = []
108-
seen = set()
109169

110-
for match in PARAM_RE.finditer(text):
111-
param = match.group(1)
112-
if param in seen:
170+
for doc_rel, extra_skip in PARAM_DOCS.items():
171+
doc_path = repo_root / doc_rel
172+
if not doc_path.exists():
113173
continue
114-
seen.add(param)
115174

116-
# Skip non-parameter identifiers
117-
if PARAM_SKIP.search(param):
118-
continue
119-
# Skip single-character names (too ambiguous: m, n, p are grid dims)
120-
if len(param) <= 1:
121-
continue
122-
# Skip names containing % (struct members like x_domain%beg are valid
123-
# but the base name before % is what matters)
124-
base = param.split("%")[0] if "%" in param else param
125-
# Check for indexed parameters: strip trailing _N (e.g., patch_icpp(1)%alpha(1))
126-
# In docs these appear as e.g. `patch_icpp(i)%vel(j)` — skip indexed refs
127-
if "(" in param or ")" in param:
128-
continue
175+
text = _strip_code_blocks(doc_path.read_text(encoding="utf-8"))
176+
seen = set()
177+
178+
# Check plain params
179+
for match in PARAM_RE.finditer(text):
180+
param = match.group(1)
181+
if param in seen:
182+
continue
183+
seen.add(param)
129184

130-
if base not in valid_params and base not in sub_params:
131-
# Check if it's a known parameter family prefix
132-
if not any(p.startswith(base) for p in valid_params):
133-
errors.append(f" equations.md references parameter '{param}' not in REGISTRY")
185+
if PARAM_SKIP.search(param):
186+
continue
187+
if len(param) <= 1:
188+
continue
189+
if param in extra_skip:
190+
continue
191+
if "(" in param or ")" in param:
192+
continue
193+
if "[" in param:
194+
continue # Bracket shorthand (e.g., x[y,z]_domain%%beg[end])
195+
196+
# Normalize %% to % for lookup
197+
normalized = param.replace("%%", "%")
198+
if not _is_valid_param(normalized, valid_params, sub_params):
199+
errors.append(f" {doc_rel} references parameter '{param}' not in REGISTRY")
200+
201+
return errors
202+
203+
204+
def check_page_refs(repo_root: Path) -> list[str]:
205+
"""Check that @ref targets in docs reference existing page identifiers."""
206+
doc_dir = repo_root / "docs" / "documentation"
207+
if not doc_dir.exists():
208+
return []
209+
210+
# Collect all @page identifiers
211+
page_ids = {"citelist"} # Doxygen built-in
212+
for md_file in doc_dir.glob("*.md"):
213+
text = md_file.read_text(encoding="utf-8")
214+
m = re.match(r"@page\s+(\w+)", text)
215+
if m:
216+
page_ids.add(m.group(1))
217+
218+
errors = []
219+
for md_file in sorted(doc_dir.glob("*.md")):
220+
text = md_file.read_text(encoding="utf-8")
221+
rel = md_file.relative_to(repo_root)
222+
for match in REF_RE.finditer(text):
223+
ref_target = match.group(1)
224+
if ref_target not in page_ids:
225+
errors.append(f" {rel} uses @ref {ref_target} but no @page with that ID exists")
134226

135227
return errors
136228

@@ -142,6 +234,7 @@ def main():
142234
all_errors.extend(check_docs(repo_root))
143235
all_errors.extend(check_cite_keys(repo_root))
144236
all_errors.extend(check_param_refs(repo_root))
237+
all_errors.extend(check_page_refs(repo_root))
145238

146239
if all_errors:
147240
print("Doc reference check failed:")

0 commit comments

Comments
 (0)