Skip to content

Commit 6813e04

Browse files
committed
Add the 2D case that measures the IB force against the MPI decomposition
Claude-Session: https://claude.ai/code/session_01HMJ7cycfo7kTFSFq5yhHLG
1 parent 1be4d30 commit 6813e04

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Immersed-boundary force under a changing MPI decomposition
2+
3+
A static cylinder in uniform flow at Re 40, Ma 0.1, on a uniform 200 x 200 grid. The body sits at the
4+
origin, which is also the center of the domain, so any decomposition with an even number of ranks in a
5+
direction puts a subdomain edge straight through it.
6+
7+
The force on a rigid body is a property of the flow. Running the same case on a different number of ranks
8+
must not change it. This case measures whether it does.
9+
10+
## Running it
11+
12+
```
13+
./mfc.sh run examples/2D_ibm_force_decomposition/case.py -n 1
14+
./mfc.sh run examples/2D_ibm_force_decomposition/case.py -n 4
15+
```
16+
17+
Each run writes `restart_data/ib_state_200.dat`: 20 doubles, `[time, Fx, Fy, Fz, Tx, Ty, Tz, ...]`.
18+
19+
```
20+
python3 -c "import numpy as np; a = np.fromfile('restart_data/ib_state_200.dat'); print(a[1], a[2])"
21+
```
22+
23+
## What it shows
24+
25+
`fd_order = 4` here, so the force integral's stencil reaches two cells, and a body cell two cells from a
26+
subdomain edge asks for finite-difference coefficients outside the interior. Before the coefficients were
27+
defined over that range they were read off the end of the array:
28+
29+
| ranks | Fx before | Fx after |
30+
| --- | --- | --- |
31+
| 1 | 1.02352019 | 1.02352019 |
32+
| 4 | 1.02933438 | 1.02351629 |
33+
34+
0.57 percent of drag appearing out of adjacent memory, against 0.0004 percent after. The single-rank
35+
answer is unchanged, because a single rank never reaches outside its own interior.
36+
37+
The size of the discrepancy is set by whatever is adjacent in memory and is not bounded by anything: on a
38+
3D sphere at Re 100 on 64 ranks the same read produced a transverse force of 1.08 times the drag on a body
39+
that has none.
40+
41+
Lift is a second, independent check: the cylinder is symmetric about y = 0, so Fy must be zero. It is
42+
about 8e-6 here in every configuration, which is the level at which the staircase representation of a
43+
circle on this grid is symmetric, and it does not move.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""The smallest case that shows MFC's immersed-boundary force depending on the MPI decomposition.
3+
4+
A static cylinder in uniform flow, Re 40, on a grid coarse enough to run on a laptop. The body sits at the
5+
origin, which is also the domain center, so a decomposition with an even number of ranks in a direction puts
6+
a subdomain edge straight through the body. Run it at one rank and at four and compare `restart_data/ib_state_200.dat`:
7+
the cylinder is symmetric about y = 0 and the lift must be zero, and the drag must not care how the domain
8+
was cut up.
9+
"""
10+
11+
import json
12+
import os
13+
14+
Re, Ma, d, gamma, U, rho = 40.0, 0.1, 1.0, 1.4, 1.0, 1.0
15+
P = rho * U**2 / (gamma * Ma**2)
16+
L = float(os.environ.get("L", 5.0))
17+
dx = float(os.environ.get("DX", 0.1))
18+
N = int(2 * L / dx)
19+
NSTEP = int(os.environ.get("NSTEP", 200))
20+
21+
case = {
22+
"run_time_info": "T",
23+
"parallel_io": "T",
24+
"prim_vars_wrt": "T",
25+
"ib_state_wrt": "T",
26+
"format": "silo",
27+
"precision": "double",
28+
"x_domain%beg": -L,
29+
"x_domain%end": L,
30+
"y_domain%beg": -L,
31+
"y_domain%end": L,
32+
"m": N - 1,
33+
"n": N - 1,
34+
"p": 0,
35+
"cyl_coord": "F",
36+
"dt": 0.3 * dx / (U + U / Ma),
37+
"t_step_start": 0,
38+
"t_step_stop": NSTEP,
39+
"t_step_save": NSTEP,
40+
"num_patches": 1,
41+
"num_fluids": 1,
42+
"model_eqns": "5eq",
43+
"alt_soundspeed": "F",
44+
"mpp_lim": "F",
45+
"mixture_err": "T",
46+
"time_stepper": "rk3",
47+
"weno_order": 5,
48+
"weno_eps": 1.0e-10,
49+
"weno_Re_flux": "T",
50+
"weno_avg": "T",
51+
"avg_state": "arithmetic",
52+
"mapped_weno": "T",
53+
"null_weights": "F",
54+
"mp_weno": "F",
55+
"riemann_solver": "hllc",
56+
"low_Mach": 2,
57+
"wave_speeds": "direct",
58+
"viscous": "T",
59+
"fd_order": int(os.environ.get("FDORDER", 4)),
60+
"patch_icpp(1)%geometry": 3,
61+
"patch_icpp(1)%x_centroid": 0.0,
62+
"patch_icpp(1)%y_centroid": 0.0,
63+
"patch_icpp(1)%length_x": 2 * L,
64+
"patch_icpp(1)%length_y": 2 * L,
65+
"patch_icpp(1)%vel(1)": U,
66+
"patch_icpp(1)%vel(2)": 0.0,
67+
"patch_icpp(1)%pres": P,
68+
"patch_icpp(1)%alpha_rho(1)": rho,
69+
"patch_icpp(1)%alpha(1)": 1.0,
70+
"fluid_pp(1)%gamma": 1.0 / (gamma - 1.0),
71+
"fluid_pp(1)%eos": "ideal_gas",
72+
"fluid_pp(1)%Re(1)": Re / (d * U),
73+
"bc_x%beg": -7,
74+
"bc_x%grcbc_in": "T",
75+
"bc_x%vel_in(1)": U,
76+
"bc_x%vel_in(2)": 0.0,
77+
"bc_x%pres_in": P,
78+
"bc_x%alpha_rho_in(1)": rho,
79+
"bc_x%alpha_in(1)": 1.0,
80+
"bc_x%end": -8,
81+
"bc_y%beg": -8,
82+
"bc_y%end": -8,
83+
"ib": "T",
84+
"num_ibs": 1,
85+
"ib_neighborhood_radius": 3,
86+
"patch_ib(1)%geometry": 2,
87+
"patch_ib(1)%x_centroid": 0.0,
88+
"patch_ib(1)%y_centroid": 0.0,
89+
"patch_ib(1)%radius": d / 2,
90+
"patch_ib(1)%slip": "F",
91+
"patch_ib(1)%moving_ibm": 0,
92+
}
93+
print(json.dumps(case, indent=4))

0 commit comments

Comments
 (0)