Skip to content

Commit d477a13

Browse files
committed
Refuse a boundary-condition patch geometry the dimensionality never applies
s_apply_boundary_patches dispatches by dimensionality -- geometry 1 in 2D, geometry 2 or 3 in 3D -- and has no else. A geometry belonging to the other case falls straight through: the patch is never applied, nothing is printed, and the face silently keeps whatever bc_[xyz] gave it. A nozzle cut into a no-slip wall with a 3D geometry in a 2D case therefore stays a solid wall. The run completes, writes output, and the jet has a velocity of exactly zero for all time with no indication anything was ignored. That is how it was found. examples/2D_bc_patch_geometry carries the case with a GEOMETRY knob: geometry 1 passes as before, GEOMETRY=3 now reports patch_bc(1)%geometry must be 1 (line segment) in 2D; geometry 3 is never applied where it used to pass and then do nothing. The 3D direction is symmetric and is refused the same way. Validator only; all 182 example cases still validate.
1 parent dc0aec1 commit d477a13

3 files changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Boundary-condition patch geometry has to match the dimensionality
2+
3+
`s_apply_boundary_patches` (`src/pre_process/m_boundary_conditions.fpp`) dispatches by dimensionality:
4+
5+
```fortran
6+
if (p > 0) then ! 3D
7+
if (patch_bc(i)%geometry == 2) call s_circle_bc(i, bc_type)
8+
else if (patch_bc(i)%geometry == 3) call s_rectangle_bc(i, bc_type)
9+
else if (n > 0) then ! 2D
10+
if (patch_bc(i)%geometry == 1) call s_line_segment_bc(i, bc_type)
11+
```
12+
13+
There is no `else`. A geometry belonging to the other dimensionality falls straight through: **the patch is
14+
never applied, nothing is printed, and the face silently keeps whatever `bc_[xyz]` gave it.**
15+
16+
That is quiet in the worst way. A nozzle cut into a no-slip wall with a 3D geometry in a 2D case simply stays
17+
a solid wall — the run completes, writes output, and the jet has a velocity of exactly zero for all time with
18+
no indication anything was ignored.
19+
20+
## Running it
21+
22+
```
23+
./mfc.sh validate examples/2D_bc_patch_geometry/case.py # geometry 1, valid in 2D
24+
GEOMETRY=3 ./mfc.sh validate examples/2D_bc_patch_geometry/case.py # a 3D geometry in a 2D case
25+
```
26+
27+
| | before | after |
28+
| --- | --- | --- |
29+
| `GEOMETRY=1` | passes | passes |
30+
| `GEOMETRY=3` | **passes, then does nothing at run time** | `patch_bc(1)%geometry must be 1 (line segment) in 2D; geometry 3 is never applied` |
31+
32+
The 3D direction is symmetric: geometry 1 in a 3D case is equally ignored, and is now equally refused.
33+
34+
## A second trap in the same corner
35+
36+
The case carries a second initial-condition patch a few cells thick at the inlet, and it is load-bearing:
37+
the Dirichlet buffer is filled by pre_process from the **initial condition at the boundary face**. A domain
38+
initialised at rest stores rest in that buffer, and the "inflow" then delivers zero for all time — again with
39+
no warning. This is not what the validator change addresses; it is noted here because the two failures look
40+
identical from the outside, and knowing that saves working out which one is in play.
41+
42+
## Scope
43+
44+
Validator only; no source change and no golden files. All 182 example cases still validate.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
"""A 2D boundary-condition patch, with the geometry the dimensionality actually supports.
3+
4+
`s_apply_boundary_patches` dispatches by dimensionality: geometry 1 (line segment) in 2D, geometry 2 (circle)
5+
or 3 (rectangle) in 3D. A geometry belonging to the other case falls straight through the dispatch -- the
6+
patch is never applied, and the face silently keeps whatever `bc_[xyz]` gave it.
7+
8+
Set GEOMETRY=3 to see the validator refuse what used to pass quietly:
9+
10+
python3 case.py # geometry 1, valid in 2D
11+
GEOMETRY=3 python3 case.py # a 3D geometry in a 2D case
12+
"""
13+
14+
import json
15+
import os
16+
17+
gamma, rho, U, Ma = 1.4, 1.0, 1.0, 0.6
18+
cs = U / Ma
19+
P = rho * cs**2 / gamma
20+
D, L, H = 1.0, 8.0, 6.0
21+
dx = D / 20
22+
N, NY = int(L / dx), int(H / dx)
23+
24+
case = {
25+
"run_time_info": "T",
26+
"parallel_io": "T",
27+
"prim_vars_wrt": "T",
28+
"format": "silo",
29+
"precision": "double",
30+
"x_domain%beg": 0.0,
31+
"x_domain%end": L,
32+
"y_domain%beg": -0.5 * H,
33+
"y_domain%end": 0.5 * H,
34+
"m": N - 1,
35+
"n": NY - 1,
36+
"p": 0,
37+
"cyl_coord": "F",
38+
"dt": 0.2 * dx / (U + cs),
39+
"t_step_start": 0,
40+
"t_step_stop": 100,
41+
"t_step_save": 100,
42+
"num_patches": 2,
43+
"num_fluids": 1,
44+
"model_eqns": "5eq",
45+
"alt_soundspeed": "F",
46+
"mpp_lim": "F",
47+
"mixture_err": "T",
48+
"time_stepper": "rk3",
49+
"weno_order": 5,
50+
"weno_eps": 1.0e-10,
51+
"weno_avg": "T",
52+
"avg_state": "arithmetic",
53+
"mapped_weno": "T",
54+
"null_weights": "F",
55+
"mp_weno": "F",
56+
"riemann_solver": "hllc",
57+
"low_Mach": 2,
58+
"wave_speeds": "direct",
59+
"viscous": "F",
60+
"fd_order": 4,
61+
"patch_icpp(1)%geometry": 3,
62+
"patch_icpp(1)%x_centroid": 0.5 * L,
63+
"patch_icpp(1)%y_centroid": 0.0,
64+
"patch_icpp(1)%length_x": L,
65+
"patch_icpp(1)%length_y": H,
66+
"patch_icpp(1)%vel(1)": 0.0,
67+
"patch_icpp(1)%vel(2)": 0.0,
68+
"patch_icpp(1)%pres": P,
69+
"patch_icpp(1)%alpha_rho(1)": rho,
70+
"patch_icpp(1)%alpha(1)": 1.0,
71+
# The Dirichlet buffer is filled by pre_process from the initial condition *at the boundary face*, so the
72+
# inflow state has to be present there. A face initialised at rest stores rest and delivers nothing.
73+
"patch_icpp(2)%geometry": 3,
74+
"patch_icpp(2)%alter_patch(1)": "T",
75+
"patch_icpp(2)%x_centroid": 2 * dx,
76+
"patch_icpp(2)%y_centroid": 0.0,
77+
"patch_icpp(2)%length_x": 4 * dx,
78+
"patch_icpp(2)%length_y": D,
79+
"patch_icpp(2)%vel(1)": U,
80+
"patch_icpp(2)%vel(2)": 0.0,
81+
"patch_icpp(2)%pres": P,
82+
"patch_icpp(2)%alpha_rho(1)": rho,
83+
"patch_icpp(2)%alpha(1)": 1.0,
84+
"fluid_pp(1)%gamma": 1.0 / (gamma - 1.0),
85+
"fluid_pp(1)%eos": "ideal_gas",
86+
# a wall with a nozzle cut into it
87+
"bc_x%beg": -16,
88+
"num_bc_patches": 1,
89+
"patch_bc(1)%dir": 1,
90+
"patch_bc(1)%loc": -1,
91+
"patch_bc(1)%type": -17,
92+
"patch_bc(1)%geometry": int(os.environ.get("GEOMETRY", 1)),
93+
"patch_bc(1)%centroid(2)": 0.0,
94+
"patch_bc(1)%length(2)": D,
95+
"bc_x%end": -8,
96+
"bc_y%beg": -8,
97+
"bc_y%end": -8,
98+
}
99+
print(json.dumps(case, indent=4))

toolchain/mfc/case_validator.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,17 @@ def check_bc_patches(self):
22172217
if geometry is None:
22182218
continue
22192219

2220+
# s_apply_boundary_patches dispatches by dimensionality: geometry 1 in 2D, 2 or 3 in 3D. A
2221+
# geometry that belongs to the other case falls through the dispatch, the patch is never applied,
2222+
# and the face silently keeps whatever bc_[xyz] gave it -- a nozzle cut into a wall simply stays a
2223+
# wall, with no warning and a jet that never starts.
2224+
p = self.get("p", 0) or 0
2225+
n = self.get("n", 0) or 0
2226+
if p > 0:
2227+
self.prohibit(geometry not in (2, 3), f"patch_bc({i})%geometry must be 2 (circle) or 3 (rectangle) in 3D; " f"geometry {geometry} is never applied")
2228+
elif n > 0:
2229+
self.prohibit(geometry != 1, f"patch_bc({i})%geometry must be 1 (line segment) in 2D; " f"geometry {geometry} is never applied")
2230+
22202231
# Line Segment BC (geometry = 1)
22212232
if geometry == 1:
22222233
self.prohibit(radius is not None, f"Line Segment Patch {i} can't have radius defined")

0 commit comments

Comments
 (0)