Skip to content

Commit b96d303

Browse files
committed
Switch 1D convergence to single-fluid Euler; add muscl_lim=0 unlimited
1D runner now uses examples/1D_euler_convergence/case.py (single-fluid, density sine wave, no non-conservative alpha equation) instead of the two-fluid advection case. The five-equation model non-conservative alpha transport degrades unlimited MUSCL to ~1st order on two-fluid problems even with identical EOS; the single-fluid Euler case gives clean rates. Add muscl_lim=0 (unlimited central-difference) to MUSCL reconstruction: - src/simulation/m_muscl.fpp: central slope = 0.5*(slopeL+slopeR) - toolchain/mfc/params/definitions.py: add 0 to choices - toolchain/mfc/case_validator.py: allow muscl_lim in {0..5} 1D and 2D convergence cases default to muscl_lim=0 for smooth problems where TVD limiters would stall at smooth extrema. Runner default CFL=0.02 so RK3 temporal error O(h^3) stays negligible for WENO5 rate-5 verification.
1 parent 7558af4 commit b96d303

7 files changed

Lines changed: 124 additions & 13 deletions

File tree

examples/1D_advection_convergence/case.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
parser.add_argument("--order", type=int, default=5, help="WENO order: 1, 3, or 5")
2020
parser.add_argument("--muscl", action="store_true", help="Use MUSCL-2 instead of WENO")
2121
parser.add_argument("--cfl", type=float, default=0.4, help="CFL number (default: 0.4)")
22+
parser.add_argument("--mp-weno", action="store_true", help="Enable MP-WENO limiter")
23+
parser.add_argument("--muscl-lim", type=int, default=1, help="MUSCL limiter: 1=minmod 2=MC 3=VanAlbada 4=VanLeer 5=Superbee")
2224
args = parser.parse_args()
2325

2426
gamma = 1.4
@@ -39,7 +41,7 @@
3941
scheme_params = {
4042
"recon_type": 2,
4143
"muscl_order": 2,
42-
"muscl_lim": 1,
44+
"muscl_lim": args.muscl_lim,
4345
}
4446
else:
4547
scheme_params = {
@@ -50,7 +52,7 @@
5052
"weno_avg": "F",
5153
"mapped_weno": "F" if args.order == 1 else "T",
5254
"null_weights": "F",
53-
"mp_weno": "F",
55+
"mp_weno": "T" if args.mp_weno else "F",
5456
}
5557

5658
print(
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python3
2+
"""
3+
1D single-fluid Euler convergence case.
4+
5+
Single fluid with a density sine wave: rho = 1 + 0.2*sin(2*pi*x).
6+
Constant velocity u=1 and pressure p=1. For this IC, the Euler equations
7+
reduce to pure advection of all variables at speed u=1. After exactly one
8+
period (T = L/u = 1), the exact solution equals the IC, so
9+
L2(rho(T) - rho(0)) measures the accumulated scheme spatial truncation error.
10+
11+
No non-conservative alpha equation — clean benchmark for WENO/MUSCL rates.
12+
"""
13+
14+
import argparse
15+
import json
16+
import math
17+
18+
parser = argparse.ArgumentParser(description="1D Euler convergence case")
19+
parser.add_argument("--mfc", type=json.loads, default="{}", metavar="DICT")
20+
parser.add_argument("-N", type=int, default=64, help="Grid points (default: 64)")
21+
parser.add_argument("--order", type=int, default=5, help="WENO order: 1, 3, or 5")
22+
parser.add_argument("--muscl", action="store_true", help="Use MUSCL-2 instead of WENO")
23+
parser.add_argument("--cfl", type=float, default=0.4, help="CFL number (default: 0.4)")
24+
parser.add_argument("--no-mapped", action="store_true", help="Disable mapped WENO")
25+
parser.add_argument("--muscl-lim", type=int, default=0, help="MUSCL limiter: 0=unlimited 1=minmod ... (default: 0)")
26+
args = parser.parse_args()
27+
28+
gamma = 1.4
29+
N = args.N
30+
m = N - 1
31+
L = 1.0
32+
dx = L / N
33+
34+
# c_sound = sqrt(gamma * p / rho) = sqrt(gamma) for p=1, rho=1
35+
c_max = math.sqrt(gamma) + 1.0 # acoustic + convective
36+
dt = args.cfl * dx / c_max
37+
T_end = 1.0
38+
Nt = max(4, math.ceil(T_end / dt))
39+
dt = T_end / Nt
40+
41+
if args.muscl:
42+
scheme_params = {
43+
"recon_type": 2,
44+
"muscl_order": 2,
45+
"muscl_lim": args.muscl_lim,
46+
}
47+
else:
48+
scheme_params = {
49+
"recon_type": 1,
50+
"weno_order": args.order,
51+
"weno_eps": 1.0e-16,
52+
"weno_Re_flux": "F",
53+
"weno_avg": "F",
54+
"mapped_weno": "F" if (args.order == 1 or args.no_mapped) else "T",
55+
"null_weights": "F",
56+
"mp_weno": "F",
57+
}
58+
59+
print(
60+
json.dumps(
61+
{
62+
"run_time_info": "F",
63+
"x_domain%beg": 0.0,
64+
"x_domain%end": L,
65+
"m": m,
66+
"n": 0,
67+
"p": 0,
68+
"dt": dt,
69+
"t_step_start": 0,
70+
"t_step_stop": Nt,
71+
"t_step_save": Nt,
72+
"num_patches": 1,
73+
"model_eqns": 2,
74+
"alt_soundspeed": "F",
75+
"num_fluids": 1,
76+
"mpp_lim": "F",
77+
"mixture_err": "F",
78+
"time_stepper": 3,
79+
"riemann_solver": 2,
80+
"wave_speeds": 1,
81+
"avg_state": 2,
82+
"bc_x%beg": -1,
83+
"bc_x%end": -1,
84+
"format": 1,
85+
"precision": 2,
86+
"prim_vars_wrt": "T",
87+
"parallel_io": "F",
88+
"patch_icpp(1)%geometry": 1,
89+
"patch_icpp(1)%x_centroid": 0.5,
90+
"patch_icpp(1)%length_x": L,
91+
"patch_icpp(1)%vel(1)": 1.0,
92+
"patch_icpp(1)%pres": 1.0,
93+
"patch_icpp(1)%alpha_rho(1)": "1.0 + 0.2 * sin(2.0 * pi * x / lx)",
94+
"patch_icpp(1)%alpha(1)": 1.0,
95+
"fluid_pp(1)%gamma": 1.0 / (gamma - 1.0),
96+
"fluid_pp(1)%pi_inf": 0.0,
97+
**scheme_params,
98+
}
99+
)
100+
)

examples/2D_isentropicvortex_convergence/case.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
parser.add_argument("-N", type=int, default=32, help="Grid points per dim (default: 32)")
1616
parser.add_argument("--order", type=int, default=5, help="WENO order: 1, 3, or 5 (default: 5)")
1717
parser.add_argument("--muscl", action="store_true", help="Use MUSCL instead of WENO")
18+
parser.add_argument("--muscl-lim", type=int, default=0, help="MUSCL limiter: 0=unlimited 1=minmod ... (default: 0)")
1819
args = parser.parse_args()
1920

2021
gamma = 1.4
@@ -34,7 +35,7 @@
3435
scheme_params = {
3536
"recon_type": 2,
3637
"muscl_order": 2,
37-
"muscl_lim": 1,
38+
"muscl_lim": args.muscl_lim,
3839
}
3940
else:
4041
scheme_params = {

src/simulation/m_muscl.fpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ contains
169169
slopeR = v_rs_ws_${XYZ}$_muscl(j, k, l, i) - v_rs_ws_${XYZ}$_muscl(j - 1, k, l, i)
170170
slope = 0._wp
171171

172-
if (muscl_lim == 1) then ! minmod
172+
if (muscl_lim == 0) then ! unlimited (central difference)
173+
slope = 5e-1_wp*(slopeL + slopeR)
174+
else if (muscl_lim == 1) then ! minmod
173175
if (slopeL*slopeR > muscl_eps) then
174176
slope = min(abs(slopeL), abs(slopeR))
175177
end if

toolchain/mfc/case_validator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"check_muscl": {
134134
"title": "MUSCL Reconstruction",
135135
"category": "Numerical Schemes",
136-
"explanation": "muscl_order must be 1 or 2. Second order requires muscl_lim in {1,2,3,4,5}.",
136+
"explanation": "muscl_order must be 1 or 2. Second order requires muscl_lim in {0,1,2,3,4,5}.",
137137
},
138138
"check_time_stepping": {
139139
"title": "Time Stepping",
@@ -790,7 +790,7 @@ def check_muscl_simulation(self):
790790
return
791791

792792
self.prohibit(muscl_order == 2 and muscl_lim is None, "muscl_lim must be defined if using muscl_order = 2")
793-
self.prohibit(muscl_lim is not None and (muscl_lim < 1 or muscl_lim > 5), "muscl_lim must be 1, 2, 3, 4, or 5")
793+
self.prohibit(muscl_lim is not None and (muscl_lim < 0 or muscl_lim > 5), "muscl_lim must be 0 (unlimited), 1, 2, 3, 4, or 5")
794794
if muscl_eps is not None:
795795
self.prohibit(muscl_eps < 0, "muscl_eps must be >= 0 (use 0 for textbook limiter behavior)")
796796

toolchain/mfc/params/definitions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,8 @@ def get_value_label(param_name: str, value: int) -> str:
609609
"value_labels": {1: "1st order", 2: "2nd order"},
610610
},
611611
"muscl_lim": {
612-
"choices": [1, 2, 3, 4, 5],
613-
"value_labels": {1: "minmod", 2: "MC", 3: "Van Albada", 4: "Van Leer", 5: "SUPERBEE"},
612+
"choices": [0, 1, 2, 3, 4, 5],
613+
"value_labels": {0: "unlimited", 1: "minmod", 2: "MC", 3: "Van Albada", 4: "Van Leer", 5: "SUPERBEE"},
614614
},
615615
# Time stepping
616616
"time_stepper": {

toolchain/mfc/test/run_convergence_1d.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/bin/env python3
22
"""
3-
Convergence-rate verification for MFC's 1D periodic advection problem.
3+
Convergence-rate verification for MFC's 1D single-fluid Euler equations.
44
5-
Two identical fluids (same EOS, rho=1) with a sine-wave volume fraction.
5+
Single fluid with a density sine wave: rho = 1 + 0.2*sin(2*pi*x), u=1, p=1.
66
After exactly one period (T=1, u=1, L=1), the exact solution equals the IC.
7-
L2(q_cons_vf1(T) - q_cons_vf1(0)) measures the accumulated scheme error.
7+
L2(rho(T) - rho(0)) measures the accumulated scheme spatial truncation error.
8+
No non-conservative alpha equation — clean benchmark for all schemes.
89
910
CFL=0.02 by default so that RK3 temporal error O(dt^3)~O(h^3) is negligible
1011
relative to the spatial error at all tested resolutions, allowing WENO5 to
@@ -16,6 +17,10 @@
1617
the 2D isentropic vortex test (run_convergence.py) verifies WENO3 rate 3 on
1718
a problem without smooth-extremum contamination.
1819
20+
MUSCL2 uses muscl_lim=0 (unlimited central-difference) by default. TVD
21+
limiters clip slopes to zero at smooth extrema and stall at 1st order on the
22+
sine wave; the unlimited limiter preserves 2nd-order convergence everywhere.
23+
1924
Usage:
2025
python toolchain/mfc/test/run_convergence_1d.py [--no-build] [--resolutions 32 64 128]
2126
"""
@@ -32,7 +37,7 @@
3237

3338
import numpy as np
3439

35-
CASE = "examples/1D_advection_convergence/case.py"
40+
CASE = "examples/1D_euler_convergence/case.py"
3641
MFC = "./mfc.sh"
3742

3843
SCHEMES = [
@@ -168,6 +173,7 @@ def main():
168173
help="Schemes to test",
169174
)
170175
parser.add_argument("--cfl", type=float, default=0.02, help="CFL number (default: 0.02; small so RK3 temporal error is negligible)")
176+
parser.add_argument("--muscl-lim", type=int, default=0, help="MUSCL limiter (0=unlimited 1=minmod ...; default: 0)")
171177
args = parser.parse_args()
172178

173179
if not args.no_build:
@@ -179,7 +185,7 @@ def main():
179185
if result.returncode != 0:
180186
sys.exit(1)
181187

182-
cfl_extra = ["--cfl", str(args.cfl)]
188+
cfl_extra = ["--cfl", str(args.cfl), "--muscl-lim", str(args.muscl_lim)]
183189

184190
results = {}
185191
for label, extra_args, expected_order, tol in SCHEMES:

0 commit comments

Comments
 (0)