Skip to content

Commit 1f9af07

Browse files
committed
Add the thin-plate force case that measures issue 1849
A 2D plate pitching about its leading edge against Jantzen et al. (2014) Fig. 10 curve C1, with the number of cells across the 2.5 percent thickness set by NCELL so the physical problem is fixed and only the resolution moves. This measures the defect rather than fixing it. The reading that matters is that the disagreement with the reference does not shrink with refinement: 20.9, 33.5, 27.9 and 26.9 percent rms at 2, 4, 8 and 16 cells across. That rules out under-resolution and points at the force computation. A second reading falls out of the same sweep: the peak converges from four cells up (4.682, 4.458, 4.833 across a fourfold refinement), while two cells is 35 percent out. A thin body does not need ten or more cells for the immersed boundary to resolve it, which matters for cost estimates.
1 parent dc0aec1 commit 1f9af07

2 files changed

Lines changed: 198 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Immersed-boundary force on a thin plate (issue #1849)
2+
3+
A 2D flat plate pitching about its leading edge, 0 → 45° on an Eldredge smoothed ramp (a = 21), K = π/8
4+
(case C1), Re_c = 300, Ma 0.2, plate thickness 2.5 % of chord. There is a published measurement to compare
5+
against:
6+
7+
> Jantzen, Taira, Granlund & Ol, *Phys. Fluids* **26**, 053606 (2014), Fig. 10, 2D panel, curve C1.
8+
9+
`C_L = F_y / (½ ρ U² c) = 2 F_y` here, with ρ = U = c = 1 and MFC's 2D force being per unit depth.
10+
11+
This case exists to **measure** the defect in #1849, not to fix it. Whoever does fix it needs a number to move.
12+
13+
## Running the sweep
14+
15+
`NCELL` sets how many cells lie across the plate thickness. The physical problem is identical at every level —
16+
same chord, same thickness, same domain, same times — so the sweep isolates the resolution requirement from
17+
any change of geometry, which a thickness sweep would confound.
18+
19+
```
20+
NCELL=2 ./mfc.sh run examples/2D_ibm_thin_plate_force/case.py # dx = 0.0125 c, 0.22 M cells
21+
NCELL=4 ./mfc.sh run examples/2D_ibm_thin_plate_force/case.py # dx = 0.00625 c, 0.90 M cells
22+
NCELL=8 ./mfc.sh run examples/2D_ibm_thin_plate_force/case.py # dx = 0.003125 c, 3.58 M cells
23+
NCELL=16 ./mfc.sh run examples/2D_ibm_thin_plate_force/case.py # dx = 0.0015625 c, 14.3 M cells
24+
```
25+
26+
`SUMMARY=1 python3 case.py` prints the grid and step count without running anything.
27+
28+
## What the sweep shows
29+
30+
| cells across the thickness | peak C_L | C_L at t = 4 | rms difference from the reference |
31+
| --- | --- | --- | --- |
32+
| 2 | 6.413 | 1.209 | 20.9 % |
33+
| 4 | 4.682 | 1.271 | 33.5 % |
34+
| 8 | 4.458 | 1.111 | 27.9 % |
35+
| 16 | 4.833 | 1.005 | 26.9 % |
36+
| reference | 6.998 | 1.521 ||
37+
38+
Two separate readings:
39+
40+
**The peak converges from four cells up.** 4.682, 4.458 and 4.833 across a fourfold refinement — a spread of
41+
8 % with no trend. Two cells is genuinely under-resolved and 35 % out; four is enough. So a thin body does not
42+
need ten or more cells before the immersed boundary resolves it, which is worth knowing on its own for cost
43+
estimates.
44+
45+
**The disagreement with the reference does not shrink.** The last column sits at 27–34 % at every resolution
46+
and shows no sign of falling as the grid refines. **That is the measurement that matters for #1849**: it rules
47+
out under-resolution as the explanation and points at the force computation itself.
48+
49+
## Why a zero-reference check is not available here
50+
51+
The obvious cheap test — a symmetric body whose true force is exactly zero — does not apply to a pitching
52+
plate, whose lift is large and unknown. For that style of check see
53+
`examples/2D_ibm_force_decomposition` (a cylinder, whose lift must be zero) and
54+
`examples/3D_ibm_neighborhood_radius`. This case trades the exact reference for a published one.
55+
56+
## Related
57+
58+
- #1849 — the force integral over body cells on a thin plate, which this measures
59+
- #1859 — an out-of-bounds coefficient read in the same force path, fixed
60+
- #1863 — a spurious transverse force on multi-rank runs, open
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
2+
"""Immersed-boundary force on a thin plate, against a published measurement (issue 1849).
3+
4+
A 2D flat plate pitching about its leading edge, 0 -> 45 degrees on an Eldredge smoothed ramp (a = 21),
5+
K = pi/8 (case C1), Re_c = 300, Ma 0.2. The plate is 2.5 percent of the chord thick, matching the experiment.
6+
7+
Jantzen, Taira, Granlund & Ol, Phys. Fluids 26, 053606 (2014), Fig. 10, 2D panel, curve C1.
8+
9+
`NCELL` sets how many cells lie across the plate thickness; the physical problem does not change with it, so
10+
the sweep isolates the immersed-boundary resolution requirement from any change of geometry. See README.md
11+
for what the sweep shows and why it matters.
12+
13+
NCELL=2 python3 case.py dx = 0.0125 c
14+
NCELL=4 python3 case.py dx = 0.00625 c (default)
15+
NCELL=8 python3 case.py dx = 0.003125 c
16+
NCELL=16 python3 case.py dx = 0.0015625 c
17+
"""
18+
19+
import json
20+
import math
21+
import os
22+
23+
U, rho, Ma, gamma, Re = 1.0, 1.0, 0.2, 1.4, 300.0
24+
P = rho * U**2 / (gamma * Ma**2)
25+
cs = math.sqrt(gamma * P / rho)
26+
K = math.pi / 8
27+
Omega = 2 * K * U # rad per c/U; pitch time = 45 deg / Omega = 1 c/U
28+
th_max = math.radians(45.0)
29+
t_p = th_max / Omega
30+
a_smooth = 21.0
31+
THICK = 0.025
32+
t0 = 2.0 # settle at 0 deg before the ramp
33+
t_end = t0 + t_p + 4.0
34+
x0, x1, y0, y1 = -2.0, 5.0, -2.5, 2.5
35+
36+
# L4 added after the first three failed to converge: refining 2 -> 4 -> 8 cells across the thickness moved
37+
# the peak lift 6.41 -> 4.68 -> 4.46 against a reference of 7.00, i.e. away from it and then stalling.
38+
# Two under-resolved answers landing near each other is not convergence. If a few cells across a thin
39+
# body is simply too few for the immersed boundary, 16 should move back toward the reference; if the
40+
# finite thickness is genuinely the difference, it should stay near 4.5.
41+
LEVELS = {"L1": 0.0125, "L2": 0.00625, "L3": 0.003125, "L4": 0.0015625}
42+
43+
44+
NCELL = int(os.environ.get("NCELL", 4))
45+
dx = THICK / NCELL
46+
m, n = int((x1 - x0) / dx) - 1, int((y1 - y0) / dx) - 1
47+
dt = 0.4 * dx / (U + cs)
48+
nt = int(t_end / dt)
49+
50+
case = {
51+
"run_time_info": "T",
52+
"parallel_io": "T",
53+
"prim_vars_wrt": "T",
54+
"ib_state_wrt": "T",
55+
"format": "silo",
56+
"precision": "double",
57+
"x_domain%beg": x0,
58+
"x_domain%end": x1,
59+
"y_domain%beg": y0,
60+
"y_domain%end": y1,
61+
"m": m,
62+
"n": n,
63+
"p": 0,
64+
"cyl_coord": "F",
65+
"dt": dt,
66+
"t_step_start": 0,
67+
"t_step_stop": nt,
68+
"t_step_save": max(1, nt // 40),
69+
"num_patches": 1,
70+
"num_fluids": 1,
71+
"model_eqns": "5eq",
72+
"alt_soundspeed": "F",
73+
"mpp_lim": "F",
74+
"mixture_err": "T",
75+
"time_stepper": "rk3",
76+
"weno_order": 5,
77+
"weno_eps": 1.0e-10,
78+
"weno_Re_flux": "T",
79+
"weno_avg": "T",
80+
"avg_state": "arithmetic",
81+
"mapped_weno": "T",
82+
"null_weights": "F",
83+
"mp_weno": "F",
84+
"riemann_solver": "hllc",
85+
"low_Mach": 2,
86+
"wave_speeds": "direct",
87+
"viscous": "T",
88+
"fd_order": 4,
89+
"patch_icpp(1)%geometry": 3,
90+
"patch_icpp(1)%x_centroid": 0.5 * (x0 + x1),
91+
"patch_icpp(1)%y_centroid": 0.5 * (y0 + y1),
92+
"patch_icpp(1)%length_x": x1 - x0,
93+
"patch_icpp(1)%length_y": y1 - y0,
94+
"patch_icpp(1)%vel(1)": U,
95+
"patch_icpp(1)%vel(2)": 0.0,
96+
"patch_icpp(1)%pres": P,
97+
"patch_icpp(1)%alpha_rho(1)": rho,
98+
"patch_icpp(1)%alpha(1)": 1.0,
99+
"fluid_pp(1)%gamma": 1.0 / (gamma - 1.0),
100+
"fluid_pp(1)%eos": "ideal_gas",
101+
"fluid_pp(1)%Re(1)": Re,
102+
"bc_x%beg": -7,
103+
"bc_x%grcbc_in": "T",
104+
"bc_x%vel_in(1)": U,
105+
"bc_x%vel_in(2)": 0.0,
106+
"bc_x%pres_in": P,
107+
"bc_x%alpha_rho_in(1)": rho,
108+
"bc_x%alpha_in(1)": 1.0,
109+
"bc_x%end": -8,
110+
"bc_x%grcbc_out": "T",
111+
"bc_x%pres_out": P,
112+
"bc_y%beg": -9,
113+
"bc_y%end": -9,
114+
"ib": "T",
115+
"num_ibs": 1,
116+
"patch_ib(1)%geometry": 3,
117+
"patch_ib(1)%x_centroid": 0.5,
118+
"patch_ib(1)%y_centroid": 0.0,
119+
"patch_ib(1)%length_x": 1.0,
120+
"patch_ib(1)%length_y": THICK,
121+
"patch_ib(1)%slip": "F",
122+
"patch_ib(1)%moving_ibm": 1,
123+
"patch_ib(1)%angles(3)": 0.0,
124+
"patch_ib(1)%angular_vel(3)": 0.0,
125+
"omega_wrt(3)": "T",
126+
}
127+
tau = f"(t - {t0})"
128+
th = f"(0.5*{th_max}*(1.0 + (log(cosh({a_smooth}*{tau})) - log(cosh({a_smooth}*({tau} - {t_p}))))/{a_smooth * t_p}))"
129+
thd = f"(0.5*{Omega}*(tanh({a_smooth}*{tau}) - tanh({a_smooth}*({tau} - {t_p}))))"
130+
case["patch_ib(1)%angular_vel(3)"] = thd
131+
case["patch_ib(1)%vel(1)"] = f"-0.5*{thd}*sin({th})"
132+
case["patch_ib(1)%vel(2)"] = f"0.5*{thd}*cos({th})"
133+
134+
if __name__ == "__main__":
135+
if os.environ.get("SUMMARY"):
136+
print(f"{NCELL} cells across the {THICK:g} c thickness: dx = {dx:g} c, " f"{m + 1} x {n + 1} = {(m + 1) * (n + 1) / 1e6:.2f} M cells, {nt} steps of dt = {dt:.2e}")
137+
else:
138+
print(json.dumps(case, indent=4))

0 commit comments

Comments
 (0)