Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/post_process/m_mpi_proxy.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ contains
call MPI_BCAST(${VAR}$, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
#:endfor

! wall-velocity members consumed by s_slip_wall/s_no_slip_wall on all ranks
#:for DIM in ['x', 'y', 'z']
#:for DIR in [1, 2, 3]
call MPI_BCAST(bc_${DIM}$%vb${DIR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
call MPI_BCAST(bc_${DIM}$%ve${DIR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
#:endfor
#:endfor

! manual: cfl_dt (runtime-computed logical), bc_io (BC-file existence)
call MPI_BCAST(cfl_dt, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
call MPI_BCAST(bc_io, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
Expand Down
18 changes: 15 additions & 3 deletions src/pre_process/m_mpi_proxy.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,28 @@ contains
call MPI_BCAST(${VAR}$, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
#:endfor

! manual: bc_x/y/z and domain bounds (registered as struct members, not in NAMELIST_VARS)
! manual: domain bounds and wall temperatures (REAL struct members, not in NAMELIST_VARS)
#:for VAR in [ 'x_domain%beg', 'x_domain%end', 'y_domain%beg', &
& 'y_domain%end', 'z_domain%beg', 'z_domain%end', &
& 'bc_x%beg', 'bc_x%end', 'bc_y%beg', 'bc_y%end', &
& 'bc_z%beg', 'bc_z%end', 'bc_x%Twall_in', 'bc_x%Twall_out', &
& 'bc_x%Twall_in', 'bc_x%Twall_out', &
& 'bc_y%Twall_in', 'bc_y%Twall_out', 'bc_z%Twall_in', &
& 'bc_z%Twall_out']
call MPI_BCAST(${VAR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
#:endfor

! manual: BC type codes (INTEGER struct members)
#:for VAR in [ 'bc_x%beg', 'bc_x%end', 'bc_y%beg', 'bc_y%end', 'bc_z%beg', 'bc_z%end']
call MPI_BCAST(${VAR}$, 1, MPI_INTEGER, 0, MPI_COMM_WORLD, ierr)
#:endfor

! wall-velocity members consumed by s_slip_wall/s_no_slip_wall on all ranks
#:for DIM in ['x', 'y', 'z']
#:for DIR in [1, 2, 3]
call MPI_BCAST(bc_${DIM}$%vb${DIR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
call MPI_BCAST(bc_${DIM}$%ve${DIR}$, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)
#:endfor
#:endfor

! manual: cfl_dt (runtime-computed logical), bc_io (BC-file existence)
call MPI_BCAST(cfl_dt, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
call MPI_BCAST(bc_io, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)
Expand Down
14 changes: 9 additions & 5 deletions toolchain/mfc/params/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,14 +842,13 @@ def _load():
_r(f"{px}sph_har_coeff({ll},{mm})", REAL)

# fluid_pp (10 fluids)
# Members present in physical_parameters: gamma, pi_inf, Re, cv, qv, qvp, G.
# mul0/ss/pv/gamma_v/M_v/mu_v/k_v/cp_v/D_v were removed from the Fortran type
# by upstream #1085/#1093 — they must NOT be registered (namelist read would crash).
for f in range(1, NF + 1):
px = f"fluid_pp({f})%"
for a, sym in [("gamma", r"\f$\gamma_k\f$"), ("pi_inf", r"\f$\pi_{\infty,k}\f$"), ("cv", r"\f$c_{v,k}\f$"), ("qv", r"\f$q_{v,k}\f$"), ("qvp", r"\f$q'_{v,k}\f$")]:
_r(f"{px}{a}", REAL, math=sym)
_r(f"{px}mul0", REAL, {"viscosity"}, math=r"\f$\mu_{l,k}\f$")
_r(f"{px}ss", REAL, {"surface_tension"}, math=r"\f$\sigma_k\f$")
for a in ["pv", "gamma_v", "M_v", "mu_v", "k_v", "cp_v", "D_v"]:
_r(f"{px}{a}", REAL, {"bubbles"})
_r(f"{px}G", REAL, {"elasticity"}, math=r"\f$G_k\f$")
_r(f"{px}Re(1)", REAL, {"viscosity"}, math=r"\f$\mathrm{Re}_k\f$ (shear)")
_r(f"{px}Re(2)", REAL, {"viscosity"}, math=r"\f$\mathrm{Re}_k\f$ (bulk)")
Expand Down Expand Up @@ -1049,11 +1048,16 @@ def _load():
_r(f"simplex_params%perturb_vel_offset({d},{j})", REAL)

# lag_params (Lagrangian bubbles)
# Members present in bubbles_lagrange_parameters: solver_approach, cluster_type,
# pressure_corrector, smooth_type, heatTransfer_model, massTransfer_model,
# write_bubbles, write_bubbles_stats, nBubs_glb, epsilonb, charwidth, valmaxvoid.
# T0/Thost/c0/rho0/x0 were removed from the Fortran type by upstream #1085/#1093
# — they must NOT be registered (namelist read would crash).
for a in ["heatTransfer_model", "massTransfer_model", "pressure_corrector", "write_bubbles", "write_bubbles_stats"]:
_r(f"lag_params%{a}", LOG, {"bubbles"})
for a in ["solver_approach", "cluster_type", "smooth_type", "nBubs_glb"]:
_r(f"lag_params%{a}", INT, {"bubbles"})
for a in ["epsilonb", "valmaxvoid", "charwidth", "c0", "rho0", "T0", "x0", "Thost"]:
for a in ["epsilonb", "valmaxvoid", "charwidth"]:
_r(f"lag_params%{a}", REAL, {"bubbles"})

# chem_params
Expand Down
14 changes: 0 additions & 14 deletions toolchain/mfc/params/descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,6 @@
(r"fluid_pp\((\d+)\)%qv", "Heat of formation for fluid {0}"),
(r"fluid_pp\((\d+)\)%qvp", "Heat of formation prime for fluid {0}"),
(r"fluid_pp\((\d+)\)%Re\((\d+)\)", "Reynolds number component {1} for fluid {0}"),
(r"fluid_pp\((\d+)\)%mul0", "Reference liquid viscosity for fluid {0}"),
(r"fluid_pp\((\d+)\)%ss", "Surface tension for fluid {0}"),
(r"fluid_pp\((\d+)\)%pv", "Vapor pressure for fluid {0}"),
(r"fluid_pp\((\d+)\)%gamma_v", "Specific heat ratio of vapor phase for fluid {0}"),
(r"fluid_pp\((\d+)\)%M_v", "Molecular weight of vapor phase for fluid {0}"),
(r"fluid_pp\((\d+)\)%mu_v", "Viscosity of vapor phase for fluid {0}"),
(r"fluid_pp\((\d+)\)%k_v", "Thermal conductivity of vapor phase for fluid {0}"),
(r"fluid_pp\((\d+)\)%cp_v", "Specific heat capacity (const. pressure) of vapor for fluid {0}"),
(r"fluid_pp\((\d+)\)%D_v", "Vapor mass diffusivity for fluid {0}"),
(r"fluid_pp\((\d+)\)%non_newtonian", "Enable Herschel-Bulkley non-Newtonian viscosity for fluid {0}"),
(r"fluid_pp\((\d+)\)%K", "HB consistency index for fluid {0}"),
(r"fluid_pp\((\d+)\)%nn", "HB flow behavior index for fluid {0}"),
Expand Down Expand Up @@ -500,11 +491,6 @@
(r"lag_params%epsilonb", "Standard deviation scaling for Gaussian smoothing"),
(r"lag_params%charwidth", "Domain virtual depth for 2D simulations"),
(r"lag_params%valmaxvoid", "Maximum permitted void fraction"),
(r"lag_params%T0", "Initial bubble temperature"),
(r"lag_params%Thost", "Host fluid temperature"),
(r"lag_params%c0", "Initial sound speed"),
(r"lag_params%rho0", "Initial density"),
(r"lag_params%x0", "Initial bubble position"),
(r"lag_params%(\w+)", "Lagrangian tracking parameter: {0}"),
# chem_params patterns - specific fields first
(r"chem_params%diffusion", "Enable species diffusion for chemistry"),
Expand Down
37 changes: 22 additions & 15 deletions toolchain/mfc/params/generators/fortran_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,11 @@ def generate_case_opt_decls_fpp() -> str:
_STRUCT_ROOTS = frozenset({"bc_x", "bc_y", "bc_z", "x_domain", "y_domain", "z_domain", "x_output", "y_output", "z_output"})

# Variables excluded from broadcast generation (derived post-broadcast or non-namelist).
_BCAST_EXCLUDE = frozenset({"muscl_eps"})
# muscl_eps was previously excluded here on the assumption that it was derived
# post-broadcast, but the derivation only fires under f_is_default(muscl_eps),
# and default values are assigned on rank 0 only. Every multi-rank MUSCL run
# therefore had rank-divergent muscl_eps. Removed from exclusion to fix the bug.
_BCAST_EXCLUDE: frozenset = frozenset()

# Post-process scalars that are namelist-bound but consumed on rank 0 only (reading/init).
# Broadcasting them would be harmless but changes the existing call set, which we preserve.
Expand Down Expand Up @@ -318,15 +322,19 @@ def _emit_bcast_group(lines: List[str], vars_list: List[str], mpi_type: str) ->
def _emit_fluid_pp(lines: List[str], target: str) -> None:
"""Emit the fluid_pp(i) member-loop broadcast block.

Members broadcast: the 13 REAL members (EOS core plus the Herschel-Bulkley
non-Newtonian set) and the LOGICAL non_newtonian flag, matching the manual
lists this generator replaces. Sim additionally: Re(1) with count=2.
Walks the registry for every fluid_pp member, emitting the MPI datatype that
matches each member's registered ParamType (the Herschel-Bulkley merge added
a LOGICAL member, so the datatype must come from the registry, not be assumed
REAL). Sim additionally broadcasts Re(1) with count=2 (kept sim-only to
preserve the historical call set; pre/post never consumed Re).
mul0/ss/pv/gamma_v/M_v/mu_v/k_v/cp_v/D_v were removed from the Fortran type by
upstream #1085/#1093 and are no longer registered.
"""
fp_real_members = ["gamma", "pi_inf", "G", "cv", "qv", "qvp", "K", "nn", "tau0", "hb_m", "mu_min", "mu_max", "mu_bulk"]
fp_members = sorted(k.split("%", 1)[1] for k in REGISTRY.all_params if k.startswith("fluid_pp(1)%") and not k.startswith("fluid_pp(1)%Re("))
lines.append(" do i = 1, num_fluids_max")
for mem in fp_real_members:
lines.append(f" call MPI_BCAST(fluid_pp(i)%{mem}, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)")
lines.append(" call MPI_BCAST(fluid_pp(i)%non_newtonian, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)")
for mem in fp_members:
ptype = REGISTRY.all_params[f"fluid_pp(1)%{mem}"].param_type
lines.append(f" call MPI_BCAST(fluid_pp(i)%{mem}, 1, {_mpi_type_for(ptype)}, 0, MPI_COMM_WORLD, ierr)")
if target == "sim":
lines.append(" call MPI_BCAST(fluid_pp(i)%Re(1), 2, mpi_p, 0, MPI_COMM_WORLD, ierr)")
lines.append(" end do")
Expand All @@ -347,14 +355,13 @@ def _emit_bub_pp(lines: List[str]) -> None:
def _emit_lag_params(lines: List[str]) -> None:
"""Emit the lag_params member broadcast block (sim-only, under bubbles_lagrange guard).

Subset of lag_params members that are actually broadcast: the fields that appear in the
existing simulation m_mpi_proxy.fpp lag_params block. The registry has additional
members (T0, Thost, c0, rho0, x0) that are not broadcast (rank-0-only).
All registered lag_params members are broadcast. T0/Thost/c0/rho0/x0 were removed
from the Fortran type by upstream #1085/#1093 and are no longer in the registry.
"""
# Hardcoded broadcast subset — matches the existing sim m_mpi_proxy exactly.
lag_log = ["heatTransfer_model", "massTransfer_model", "pressure_corrector", "write_bubbles", "write_bubbles_stats"]
lag_int = ["cluster_type", "nBubs_glb", "smooth_type", "solver_approach"]
lag_real = ["charwidth", "epsilonb", "valmaxvoid"]
# Walk the registry for lag_params members, split by type.
lag_log = sorted(k.split("%", 1)[1] for k in REGISTRY.all_params if k.startswith("lag_params%") and REGISTRY.all_params[k].param_type == ParamType.LOG)
lag_int = sorted(k.split("%", 1)[1] for k in REGISTRY.all_params if k.startswith("lag_params%") and REGISTRY.all_params[k].param_type in (ParamType.INT, ParamType.ANALYTIC_INT))
lag_real = sorted(k.split("%", 1)[1] for k in REGISTRY.all_params if k.startswith("lag_params%") and REGISTRY.all_params[k].param_type in _REAL_TYPES)
lines.append(" if (bubbles_lagrange) then")
for mem in sorted(lag_log):
lines.append(f" call MPI_BCAST(lag_params%{mem}, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD, ierr)")
Expand Down
99 changes: 95 additions & 4 deletions toolchain/mfc/params_tests/test_fortran_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,9 @@ def test_generate_bcast_fpp_case_opt_guard_sim():
assert "num_fluids" in guard_body
assert "mapped_weno" in guard_body

# muscl_eps must NOT appear anywhere (it is excluded — derived post-broadcast)
assert "muscl_eps" not in out
# muscl_eps IS inside the case-opt guard (it is sim-only, non-CASE_OPT_PARAM,
# so it appears in the real-scalars section, which is outside the case-opt block)
assert "call MPI_BCAST(muscl_eps, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)" in out


def test_generate_bcast_fpp_case_opt_not_in_pre_post():
Expand Down Expand Up @@ -504,8 +505,6 @@ def test_generate_bcast_fpp_excludes_manual_residue():
assert "m_glb" not in out, f"{target}: m_glb should be manual"
assert "n_glb" not in out, f"{target}: n_glb should be manual"
assert "p_glb" not in out, f"{target}: p_glb should be manual"
# muscl_eps is excluded (derived post-broadcast)
assert "muscl_eps" not in out, f"{target}: muscl_eps should be excluded"

sim = generate_bcast_fpp("sim")
# shear_stress, bulk_stress, bodyForces are derived (non-namelist)
Expand All @@ -530,3 +529,95 @@ def test_generate_bcast_fpp_bad_target():

with pytest.raises(ValueError, match="Unknown target"):
generate_bcast_fpp("bad")


def test_generate_bcast_fpp_muscl_eps_now_broadcast():
"""muscl_eps is broadcast for sim (latent-bug fix: derivation is rank-0-only).

Previously excluded via _BCAST_EXCLUDE; every multi-rank MUSCL run had
rank-divergent muscl_eps because f_is_default() only fires on rank 0.
"""
from mfc.params.generators.fortran_gen import generate_bcast_fpp

sim = generate_bcast_fpp("sim")
assert "call MPI_BCAST(muscl_eps, 1, mpi_p, 0, MPI_COMM_WORLD, ierr)" in sim

# muscl_eps is sim-only; must not appear in pre/post
pre = generate_bcast_fpp("pre")
post = generate_bcast_fpp("post")
assert "muscl_eps" not in pre
assert "muscl_eps" not in post


def test_generate_bcast_fpp_fluid_pp_registry_walk():
"""fluid_pp emitter walks registry; no dead members (mul0/ss/pv/gamma_v/M_v/mu_v/k_v/cp_v/D_v removed upstream).

Re(1) count=2 is sim-only; all other registered REAL members appear in all three
targets.
"""
from mfc.params.generators.fortran_gen import generate_bcast_fpp

sim = generate_bcast_fpp("sim")
pre = generate_bcast_fpp("pre")
post = generate_bcast_fpp("post")

# All registered members appear in every target
for mem in ("gamma", "pi_inf", "cv", "qv", "qvp", "G"):
for out, t in [(sim, "sim"), (pre, "pre"), (post, "post")]:
assert f"fluid_pp(i)%{mem}" in out, f"{t}: fluid_pp(i)%{mem} missing"

# Re(1) count=2 is sim-only
assert "fluid_pp(i)%Re(1)" in sim
assert "fluid_pp(i)%Re(1)" not in pre
assert "fluid_pp(i)%Re(1)" not in post

# Dead members must not appear
for dead in ("mul0", "ss", "pv", "gamma_v", "M_v", "mu_v", "k_v", "cp_v", "D_v"):
for out, t in [(sim, "sim"), (pre, "pre"), (post, "post")]:
assert f"fluid_pp(i)%{dead}" not in out, f"{t}: dead member fluid_pp(i)%{dead} present"


def test_generate_bcast_fpp_fluid_pp_member_datatypes():
# The HB merge added a LOGICAL fluid_pp member; datatypes must come from the
# registry, not be assumed REAL.
from mfc.params.generators.fortran_gen import generate_bcast_fpp

sim = generate_bcast_fpp("sim")
assert "call MPI_BCAST(fluid_pp(i)%non_newtonian, 1, MPI_LOGICAL," in sim
assert "call MPI_BCAST(fluid_pp(i)%tau0, 1, mpi_p," in sim
assert "non_newtonian, 1, mpi_p" not in sim


def test_generate_bcast_fpp_lag_params_registry_walk():
"""lag_params emitter walks registry; no dead members (T0/Thost/c0/rho0/x0 removed upstream)."""
from mfc.params.generators.fortran_gen import generate_bcast_fpp

sim = generate_bcast_fpp("sim")

# All registered members must appear
for mem in ("solver_approach", "cluster_type", "smooth_type", "nBubs_glb"):
assert f"lag_params%{mem}" in sim, f"lag_params%{mem} missing from sim"
for mem in ("heatTransfer_model", "massTransfer_model", "pressure_corrector", "write_bubbles", "write_bubbles_stats"):
assert f"lag_params%{mem}" in sim, f"lag_params%{mem} missing from sim"
for mem in ("epsilonb", "charwidth", "valmaxvoid"):
assert f"lag_params%{mem}" in sim, f"lag_params%{mem} missing from sim"

# Dead members must not appear
for dead in ("T0", "Thost", "c0", "rho0", "x0"):
assert f"lag_params%{dead}" not in sim, f"dead member lag_params%{dead} present in sim"


def test_mpi_proxy_residue_pins_wall_velocity_and_bc_datatypes():
"""The vb/ve wall-velocity broadcasts and integer BC datatypes live in
hand-written residue (not codegen); pin them so an edit or merge conflict
that drops them fails loudly."""
import pathlib

root = pathlib.Path(__file__).resolve().parents[3]
for target in ("pre_process", "post_process"):
src = (root / "src" / target / "m_mpi_proxy.fpp").read_text()
assert "bc_${DIM}$%vb${DIR}$" in src, f"{target}: vb broadcasts missing"
assert "bc_${DIM}$%ve${DIR}$" in src, f"{target}: ve broadcasts missing"
assert "'bc_x%beg', 'bc_x%end', 'bc_y%beg', 'bc_y%end', 'bc_z%beg', 'bc_z%end']" in src
seg = src.split("'bc_z%beg', 'bc_z%end']", 1)[1]
assert "MPI_INTEGER" in seg.split("#:endfor")[0], f"{target}: BC codes not MPI_INTEGER"
Loading