Skip to content

Commit 202033e

Browse files
sbryngelsonclaude
andauthored
Add unified API landing page and fix footer layout (#1169)
* Add unified API landing page and fix footer layout - Add docs/api/ Doxygen target with a landing page linking to Pre-Process, Simulation, and Post-Process API docs - Consolidate sidebar from 4 links to 2: "User Guide" and "API Documentation" (highlights for all API sub-projects) - Unify browser tab title to "MFC" across all Doxygen targets - Fix footer blank space caused by missing #nav-path element that navtree.js needs for content height calculation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add auto-generated architecture page with module map - Add docs/documentation/architecture.md.in template with hand-written sections (pipeline, data structures, simulation loop, MPI, extending) - Add docs/gen_architecture.py to generate module map from source briefs - Add docs/module_categories.json as single source of truth for groupings - Add linter check: warns when a module is missing from categories JSON - Update contributing guide with step to register new modules - Link architecture page from User Guide sidebar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Mach 1.5 shock-helium bubble simulation to gallery New entry on the landing site gallery with thumbnail from the YouTube video (https://youtube.com/watch?v=zDJoe0NYZsQ), run on a single A100 on Phoenix in under 1 hour. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add dog flow simulation and fix button grid centering - Add flow-over-dog entry (4 B200s on HiPerGator, 1h) to landing page and Doxygen gallery with thumbnail - Add shock-helium entry to landing page inline sims array (was only in simulations.json) - Fix nav button grid: grid-cols-7 → grid-cols-6 after API button removal YouTube: https://youtube.com/watch?v=Xhvr2kkQa30 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add focused ultrasound kidney stone simulation to gallery Focused ultrasound pulse impinging a phantom kidney stone with stress visualization, by J.-S. Spratt. 10 V100s on Bridges-2, 2h. YouTube: https://youtube.com/watch?v=z8j3NH-Y6i0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add architecture to pre-seeded page_ids in lint_docs.py architecture.md is gitignored (generated at build time), so the linter glob never finds its anchor. Pre-seed it like the other generated pages to prevent CI failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c612b6a commit 202033e

20 files changed

Lines changed: 441 additions & 182 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ docs/documentation/*-example.png
4545
docs/documentation/examples.md
4646
docs/documentation/case_constraints.md
4747
docs/documentation/physics_constraints.md
48+
docs/documentation/architecture.md
49+
docs/pre_process/readme.md
50+
docs/simulation/readme.md
51+
docs/post_process/readme.md
52+
docs/api/readme.md
4853

4954
examples/*batch/*/
5055
examples/**/D/*

CMakeLists.txt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,9 +803,10 @@ if (MFC_DOCUMENTATION)
803803
\"${CMAKE_CURRENT_SOURCE_DIR}/docs/custom.css\"")
804804

805805
# > Generate Documentation & Landing Page
806-
GEN_DOCS(pre_process "MFC: Pre-Process")
807-
GEN_DOCS(simulation "MFC: Simulation")
808-
GEN_DOCS(post_process "MFC: Post-Process")
806+
GEN_DOCS(pre_process "MFC")
807+
GEN_DOCS(simulation "MFC")
808+
GEN_DOCS(post_process "MFC")
809+
GEN_DOCS(api "MFC")
809810
GEN_DOCS(documentation "MFC")
810811

811812
# Generate API landing pages for pre_process, simulation, post_process.
@@ -826,6 +827,7 @@ if (MFC_DOCUMENTATION)
826827
add_dependencies(pre_process_doxygen gen_api_landing)
827828
add_dependencies(simulation_doxygen gen_api_landing)
828829
add_dependencies(post_process_doxygen gen_api_landing)
830+
add_dependencies(api_doxygen gen_api_landing)
829831

830832
# Fix @file/@brief headers to match actual module/program declarations.
831833
# Handles mixed-case Fortran names and catches stale module renames.
@@ -846,6 +848,24 @@ if (MFC_DOCUMENTATION)
846848
add_dependencies(simulation_doxygen fix_file_briefs)
847849
add_dependencies(post_process_doxygen fix_file_briefs)
848850

851+
# Generate architecture.md from template + module_categories.json + source briefs.
852+
add_custom_command(
853+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/gen-architecture.stamp"
854+
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_architecture.py"
855+
"${CMAKE_CURRENT_SOURCE_DIR}/docs/module_categories.json"
856+
"${CMAKE_CURRENT_SOURCE_DIR}/docs/documentation/architecture.md.in"
857+
${pre_process_FPPs} ${pre_process_F90s}
858+
${simulation_FPPs} ${simulation_F90s}
859+
${post_process_FPPs} ${post_process_F90s}
860+
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_architecture.py"
861+
"${CMAKE_CURRENT_SOURCE_DIR}"
862+
COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_BINARY_DIR}/gen-architecture.stamp"
863+
COMMENT "Generating architecture page"
864+
VERBATIM
865+
)
866+
add_custom_target(gen_architecture DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/gen-architecture.stamp")
867+
add_dependencies(documentation_doxygen gen_architecture)
868+
849869
# Inject per-page last-updated dates into documentation markdown files.
850870
# Runs after auto-generated .md files exist, before Doxygen processes them.
851871
# Uses a stamp file so it only runs once per build.

docs/custom.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
* Overrides for doxygen-awesome theme
44
*/
55

6+
/* Hide empty nav-path footer (kept for navtree.js height calculation) */
7+
#nav-path {
8+
height: 0;
9+
overflow: hidden;
10+
}
11+
612
/* Cross-navigation panel at top of sidebar */
713
#mfc-nav {
814
display: flex;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Code Architecture {#architecture}
2+
3+
This page explains how MFC's source code is organized, how data flows through the solver, and where to find things. Read this before diving into the source.
4+
5+
## Three-Phase Pipeline
6+
7+
MFC runs as three separate executables that communicate via binary files on disk:
8+
9+
```
10+
pre_process ──> simulation ──> post_process
11+
(grid + (time (derived
12+
initial advance) quantities +
13+
conditions) visualization)
14+
```
15+
16+
| Phase | Entry Point | What It Does |
17+
|-------|-------------|--------------|
18+
| **Pre-Process** | `src/pre_process/p_main.f90` | Generates the computational grid and initial conditions from patch definitions. Writes binary grid and state files. |
19+
| **Simulation** | `src/simulation/p_main.fpp` | Reads the initial state and advances the governing equations in time. Periodically writes solution snapshots. |
20+
| **Post-Process** | `src/post_process/p_main.fpp` | Reads snapshots, computes derived quantities (vorticity, Schlieren, etc.), and writes Silo/HDF5 files for VisIt or ParaView. |
21+
22+
Each phase is an independent MPI program. The simulation phase is where nearly all compute time is spent.
23+
24+
## Directory Layout
25+
26+
```
27+
src/
28+
common/ Shared modules used by all three phases
29+
pre_process/ Pre-process source (grid generation, patch construction)
30+
simulation/ Simulation source (solver core, physics models)
31+
post_process/ Post-process source (derived quantities, formatted I/O)
32+
```
33+
34+
Shared modules in `src/common/` include MPI communication, derived types, variable conversion, and utility functions. They are compiled into each phase.
35+
36+
## Key Data Structures
37+
38+
Two arrays carry the solution through the entire simulation:
39+
40+
| Variable | Contents | When Used |
41+
|----------|----------|-----------|
42+
| `q_cons_vf` | **Conservative** variables: \f$\alpha\rho\f$, \f$\rho u\f$, \f$\rho v\f$, \f$\rho w\f$, \f$E\f$, \f$\alpha\f$ | Storage, time integration, I/O |
43+
| `q_prim_vf` | **Primitive** variables: \f$\rho\f$, \f$u\f$, \f$v\f$, \f$w\f$, \f$p\f$, \f$\alpha\f$ | Reconstruction, Riemann solving, physics |
44+
45+
Both are `vector_field` types (defined in `m_derived_types`), which are arrays of `scalar_field`. Each `scalar_field` wraps a 3D real array `sf(0:m, 0:n, 0:p)` representing one variable on the grid.
46+
47+
The index layout within `q_cons_vf` depends on the flow model:
48+
49+
```
50+
For model_eqns == 2 (5-equation, multi-fluid):
51+
52+
Index: 1 .. num_fluids | num_fluids+1 .. +num_vels | E_idx | adv_idx
53+
Meaning: alpha*rho_k | momentum components | energy | volume fractions
54+
```
55+
56+
Additional variables are appended for bubbles, elastic stress, magnetic fields, or chemistry species when those models are enabled. The total count is `sys_size`.
57+
58+
## The Simulation Loop
59+
60+
The simulation advances the solution through this call chain each time step:
61+
62+
```
63+
p_main (time-step loop)
64+
└─ s_perform_time_step
65+
├─ s_compute_dt [adaptive CFL time step]
66+
└─ s_tvd_rk [Runge-Kutta stages]
67+
├─ s_compute_rhs [assemble dq/dt]
68+
│ ├─ s_convert_conservative_to_primitive_variables
69+
│ ├─ s_populate_variables_buffers [MPI halo exchange]
70+
│ └─ for each direction (x, y, z):
71+
│ ├─ s_reconstruct_cell_boundary_values [WENO]
72+
│ ├─ s_riemann_solver [HLL/HLLC/HLLD]
73+
│ ├─ s_compute_advection_source_term [flux divergence]
74+
│ └─ (physics source terms: viscous, bubbles, etc.)
75+
├─ RK update: q_cons = weighted combination of stages
76+
├─ s_apply_bodyforces [if enabled]
77+
├─ s_pressure_relaxation [if 6-equation model]
78+
└─ s_ibm_correct_state [if immersed boundaries]
79+
```
80+
81+
### What happens at each stage
82+
83+
1. **Conservative → Primitive**: Convert stored `q_cons_vf` to `q_prim_vf` (density, velocity, pressure) using the equation of state. This is done by `m_variables_conversion`.
84+
85+
2. **MPI Halo Exchange**: Ghost cells at subdomain boundaries are filled by communicating with neighbor ranks. Handled by `m_mpi_proxy`.
86+
87+
3. **WENO Reconstruction** (`m_weno`): For each coordinate direction, reconstruct left and right states at cell faces from cell-average primitives using high-order weighted essentially non-oscillatory stencils.
88+
89+
4. **Riemann Solver** (`m_riemann_solvers`): At each cell face, solve the Riemann problem between left and right states to compute intercell fluxes. Available solvers: HLL, HLLC, HLLD.
90+
91+
5. **Flux Differencing** (`m_rhs`): Accumulate the RHS as \f$\partial q / \partial t = -\frac{1}{\Delta x}(F_{j+1/2} - F_{j-1/2})\f$ plus source terms (viscous stress, surface tension, bubble dynamics, body forces, etc.).
92+
93+
6. **Runge-Kutta Update** (`m_time_steppers`): Combine the RHS with the current state using TVD Runge-Kutta coefficients (1st, 2nd, or 3rd order SSP).
94+
95+
## Module Map
96+
97+
MFC has ~80 Fortran modules organized by function. Here is where to look for what:
98+
99+
<!-- MODULE_MAP -->
100+
101+
## MPI Parallelization
102+
103+
The computational domain is decomposed into subdomains via `MPI_Cart_create`. Each rank owns a contiguous block of cells in (x, y, z). Ghost cells of width `buff_size` surround each subdomain and are filled by halo exchange before each RHS evaluation.
104+
105+
On GPUs, the same domain decomposition applies. GPU kernels operate on the local subdomain, with explicit host-device transfers for MPI communication (unless GPU-aware MPI / RDMA is available).
106+
107+
## Adding New Physics
108+
109+
To add a new source term or physics model:
110+
111+
1. **Create a module** in `src/simulation/` (e.g., `m_my_model.fpp`)
112+
2. **Add initialization/finalization** subroutines called from `m_start_up`
113+
3. **Add RHS contributions** called from the dimensional loop in `m_rhs:s_compute_rhs`
114+
4. **Add parameters** to `m_global_parameters` and input validation to `m_checker`
115+
5. **Add a module-level brief** (enforced by the linter in `lint_docs.py`)
116+
6. **Add the module to `docs/module_categories.json`** so it appears in this page
117+
118+
Follow the pattern of existing modules like `m_body_forces` (simple) or `m_viscous` (more involved) as a template.

docs/documentation/contributing.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@ contains
536536
end module m_my_feature
537537
```
538538

539+
**Step 3: Register the module in the architecture docs**
540+
541+
Add your module name to the appropriate category in `docs/module_categories.json`. This ensures it appears on the @ref architecture "Code Architecture" page. The precheck linter will fail if a module is missing from this file.
542+
539543
Key conventions:
540544
- `private` by default, explicitly `public` for the module API
541545
- Initialize/finalize subroutines for allocation lifecycle

docs/documentation/readme.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Welcome to the Multi-component Flow Code (MFC) documentation.
2424

2525
## Advanced Topics
2626

27+
- @ref architecture "Code Architecture" - How the source code is organized, data flow, and module map
2728
- @ref expectedPerformance "Performance" - Optimization and benchmarks
2829
- @ref gpuParallelization "GPU Parallelization" - GPU macro API (developer reference)
2930
- @ref docker "Containers" - Docker usage
@@ -32,9 +33,7 @@ Welcome to the Multi-component Flow Code (MFC) documentation.
3233
## Development
3334

3435
- @ref contributing "Contributing" - Developer guide and coding standards
35-
- [Pre-Process API](../pre_process/index.html) - Source code reference for mesh generation and initial conditions
36-
- [Simulation API](../simulation/index.html) - Source code reference for the flow solver
37-
- [Post-Process API](../post_process/index.html) - Source code reference for data extraction and visualization
36+
- [API Documentation](../api/index.html) - Source code reference for all three components
3837

3938
## About
4039

docs/footer.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<!-- HTML footer for doxygen 1.9.1-->
22
<!-- start footer part -->
33
<!--BEGIN GENERATE_TREEVIEW-->
4+
<div id="nav-path" class="navpath">
5+
<ul></ul>
6+
</div>
47
<!--END GENERATE_TREEVIEW-->
58
<!--BEGIN !GENERATE_TREEVIEW-->
69
<!--END !GENERATE_TREEVIEW-->

docs/gen_api_landing.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/usr/bin/env python3
2-
"""Generate API landing pages for pre_process, simulation, and post_process.
2+
"""Generate API landing pages for MFC documentation.
33
44
Usage: python3 gen_api_landing.py [source_dir]
55
source_dir defaults to current directory.
66
77
Scans src/{target}/*.fpp,*.f90 and src/common/*.fpp,*.f90 to produce module
8-
tables in docs/{target}/readme.md. Intro text is defined below per target.
8+
tables in docs/{target}/readme.md. Also generates a unified API landing page
9+
at docs/api/readme.md that links to all three sub-project APIs.
910
"""
1011

1112
from __future__ import annotations
@@ -149,3 +150,31 @@ def get_modules(directory: Path) -> list[tuple[str, str]]:
149150
out.parent.mkdir(parents=True, exist_ok=True)
150151
out.write_text("\n".join(lines), encoding="utf-8")
151152
print(f"Generated {out}")
153+
154+
# --- Unified API landing page ---
155+
api_lines = [
156+
"@mainpage API Documentation",
157+
"",
158+
"MFC's source code is organized into three components that form a complete "
159+
"simulation pipeline. Each component has full module-level API documentation.",
160+
"",
161+
]
162+
163+
for target, info in TARGETS.items():
164+
label = info["title"].replace("MFC ", "")
165+
api_lines.append(f"### [{label}](../{target}/index.html)")
166+
api_lines.append("")
167+
api_lines.append(info["intro"])
168+
api_lines.append("")
169+
170+
api_lines.append(
171+
"All three components share a set of **common modules** for MPI communication, "
172+
"variable conversion, derived types, and utility functions. "
173+
"These are documented within each component's API reference."
174+
)
175+
api_lines.append("")
176+
177+
api_out = src_dir / "docs" / "api" / "readme.md"
178+
api_out.parent.mkdir(parents=True, exist_ok=True)
179+
api_out.write_text("\n".join(api_lines), encoding="utf-8")
180+
print(f"Generated {api_out}")

docs/gen_architecture.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env python3
2+
"""Generate architecture.md from template + auto-generated module map.
3+
4+
Usage: python3 gen_architecture.py [source_dir]
5+
source_dir defaults to current directory.
6+
7+
Reads docs/documentation/architecture.md.in as a template, generates the
8+
module map section from docs/module_categories.json and source file briefs,
9+
and writes the final docs/documentation/architecture.md.
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import json
15+
import re
16+
import sys
17+
from pathlib import Path
18+
19+
src_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
20+
21+
_BRIEF_RE = re.compile(r"^!>\s*@brief\s+(.+)", re.IGNORECASE)
22+
23+
24+
def _extract_brief(path: Path) -> str:
25+
"""Extract the module-level @brief from a Fortran source file."""
26+
lines = path.read_text(encoding="utf-8").splitlines()
27+
parts: list[str] = []
28+
in_file_block = False
29+
collecting = False
30+
for line in lines[:40]:
31+
stripped = line.strip()
32+
if stripped.startswith("!>") and not stripped.startswith("!> @brief"):
33+
in_file_block = True
34+
continue
35+
if in_file_block and stripped.startswith("!!"):
36+
continue
37+
if in_file_block:
38+
in_file_block = False
39+
m = _BRIEF_RE.match(stripped)
40+
if m and not collecting:
41+
brief_text = m.group(1).strip()
42+
if re.match(r"Contains\s+(module|program)\s+\w+", brief_text):
43+
continue
44+
parts.append(brief_text)
45+
collecting = True
46+
continue
47+
if collecting:
48+
if stripped.startswith("!!"):
49+
parts.append(stripped.lstrip("! ").strip())
50+
else:
51+
break
52+
if not parts:
53+
return ""
54+
text = " ".join(parts)
55+
dot = text.find(". ")
56+
if dot != -1:
57+
text = text[:dot]
58+
return text.rstrip(". ").strip()
59+
60+
61+
def _find_module_file(name: str) -> Path | None:
62+
"""Find the source file for a module name across all src/ directories."""
63+
for subdir in ("common", "pre_process", "simulation", "post_process"):
64+
for ext in (".fpp", ".f90", ".F90"):
65+
path = src_dir / "src" / subdir / (name + ext)
66+
if path.exists():
67+
return path
68+
return None
69+
70+
71+
def generate_module_map() -> str:
72+
"""Generate the module map markdown from categories + source briefs."""
73+
categories_file = src_dir / "docs" / "module_categories.json"
74+
categories = json.loads(categories_file.read_text(encoding="utf-8"))
75+
76+
lines: list[str] = []
77+
for entry in categories:
78+
cat = entry["category"]
79+
modules = entry["modules"]
80+
81+
lines.append(f"### {cat}")
82+
lines.append("| Module | Role |")
83+
lines.append("|--------|------|")
84+
85+
for mod in modules:
86+
path = _find_module_file(mod)
87+
brief = _extract_brief(path) if path else ""
88+
lines.append(f"| `{mod}` | {brief} |")
89+
90+
lines.append("")
91+
92+
return "\n".join(lines)
93+
94+
95+
def main() -> None:
96+
template = src_dir / "docs" / "documentation" / "architecture.md.in"
97+
output = src_dir / "docs" / "documentation" / "architecture.md"
98+
99+
text = template.read_text(encoding="utf-8")
100+
module_map = generate_module_map()
101+
text = text.replace("<!-- MODULE_MAP -->", module_map)
102+
103+
output.write_text(text, encoding="utf-8")
104+
print(f"Generated {output}")
105+
106+
107+
if __name__ == "__main__":
108+
main()

0 commit comments

Comments
 (0)