Skip to content

Commit 16126c6

Browse files
committed
Require the relaxation targets that grcbc_out and grcbc_vel_out read
m_cbc.fpp relaxes a GRCBC outflow toward a prescribed pressure: L(adv%end) = c*(1 - Ma)*(pres - pres_out(dir))/Del_out(dir) and grcbc_vel_out reads vel_out through dir_idx, so which component is the normal one depends on the direction. Neither target was required. Setting grcbc_out without pres_out validated cleanly, then relaxed the boundary toward an undefined value. The failure mode is silent and expensive to chase. On a uniform quiescent 2D field with no body and no disturbance, density in the last cell of the outflow direction walked away linearly at 0.032 per step from step 1, crossed zero near step 31, and the run aborted on ICFL at step 57 with nothing in the output naming the boundary condition. ICFL sat at exactly 0.4000 for 56 steps and then jumped to 1.3e15 in one step, so it did not read as a boundary problem either; only dumping the field every step and finding every deviation pinned to cell i = m identified it. check_grcbc now requires bc_<dir>%pres_out when grcbc_out is set, and bc_<dir>%vel_out(1..num_dims) when grcbc_vel_out is set. Requiring every component rather than guessing the normal one follows check_synthetic_turbulence, and avoids the direction-blindness that #1854 corrects on the inflow side, where vel_in(1) is not the normal component for a y or z boundary. Verified it rejects nothing in tree: all 181 example cases generate and validate, 9 of which use grcbc_out or grcbc_vel_out, and the full validator suite passes (72 tests). Three regression tests cover the rejection, the acceptance once targets are given, and that the third velocity component is required only in 3D. Made with Claude Code.
1 parent ec783a8 commit 16126c6

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

toolchain/mfc/case_validator.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,7 @@ def check_continuum_damage(self):
17831783

17841784
def check_grcbc(self):
17851785
"""Checks Generalized Relaxation Characteristics BC (simulation)"""
1786+
num_dims = 3 if (self.get("p", 0) or 0) > 0 else (2 if (self.get("n", 0) or 0) > 0 else 1)
17861787
for dir in ["x", "y", "z"]:
17871788
grcbc_in = self.get(f"bc_{dir}%grcbc_in", "F") == "T"
17881789
grcbc_out = self.get(f"bc_{dir}%grcbc_out", "F") == "T"
@@ -1796,8 +1797,23 @@ def check_grcbc(self):
17961797
if grcbc_out:
17971798
# Check if EITHER beg OR end is set to -8
17981799
self.prohibit(bc_beg != -8 and bc_end != -8, f"Subsonic Outflow (grcbc_out) requires bc_{dir}%beg = -8 or bc_{dir}%end = -8")
1800+
# m_cbc.fpp relaxes the outflow toward this pressure:
1801+
# L(adv%end) = c*(1 - Ma)*(pres - pres_out(dir))/Del_out(dir)
1802+
# Left unset it relaxes toward an undefined target, which is silent: the boundary cell simply
1803+
# walks away, and the run aborts on ICFL tens of steps later with nothing pointing at the BC.
1804+
self.prohibit(
1805+
not self.is_set(f"bc_{dir}%pres_out"),
1806+
f"bc_{dir}%pres_out must be specified when bc_{dir}%grcbc_out is enabled",
1807+
)
17991808
if grcbc_vel_out:
18001809
self.prohibit(bc_beg != -8 and bc_end != -8, f"Subsonic Outflow Velocity (grcbc_vel_out) requires bc_{dir}%beg = -8 or bc_{dir}%end = -8")
1810+
# The kernel reads vel_out through dir_idx, so which component is the normal one depends on the
1811+
# direction. Require all num_dims components rather than guess, matching check_synthetic_turbulence.
1812+
for d in range(1, num_dims + 1):
1813+
self.prohibit(
1814+
not self.is_set(f"bc_{dir}%vel_out({d})"),
1815+
f"bc_{dir}%vel_out({d}) must be specified for all num_dims when bc_{dir}%grcbc_vel_out is enabled",
1816+
)
18011817

18021818
def check_probe_output(self):
18031819
"""Checks probe output requirements (simulation)"""

toolchain/mfc/test_case_validator.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,31 @@ def test_accepts_and_requires(self):
529529
self.assertRejects({**BASE, **self.VINET, "fluid_pp(1)%mg_s2": 0.1}, "fluid_pp(1)%mg_* are only read when")
530530
for k in ("gamma", "pi_inf"):
531531
self.assertRejects({**BASE, **self.VINET, f"fluid_pp(1)%{k}": 1.0}, f"fluid_pp(1)%{k} is not read with eos = 'vinet'")
532+
533+
534+
class TestGrcbcOutflowTargets(ConstraintTestCase):
535+
"""grcbc_out and grcbc_vel_out relax toward targets that must actually be given.
536+
537+
m_cbc.fpp computes L(adv%end) = c*(1 - Ma)*(pres - pres_out(dir))/Del_out(dir), and reads
538+
vel_out through dir_idx so which component is the normal one depends on direction. Left
539+
unset, the relaxation pulls toward an undefined target and the boundary cell walks away:
540+
the failure surfaces tens of steps later as an ICFL abort with nothing naming the BC.
541+
"""
542+
543+
OUT = {"bc_x%beg": -7, "bc_x%end": -8, "bc_x%grcbc_in": "T", "bc_x%grcbc_out": "T"}
544+
545+
def test_grcbc_out_requires_pres_out(self):
546+
self.assertRejects({**BASE_2D, **self.OUT}, "bc_x%pres_out must be specified")
547+
self.assertAccepts({**BASE_2D, **self.OUT, "bc_x%pres_out": 1.0})
548+
549+
def test_grcbc_vel_out_requires_every_component(self):
550+
p = {**BASE_2D, **self.OUT, "bc_x%pres_out": 1.0, "bc_x%grcbc_vel_out": "T"}
551+
self.assertRejects(p, "bc_x%vel_out(1) must be specified")
552+
self.assertRejects({**p, "bc_x%vel_out(1)": 1.0}, "bc_x%vel_out(2) must be specified")
553+
self.assertAccepts({**p, "bc_x%vel_out(1)": 1.0, "bc_x%vel_out(2)": 0.0})
554+
555+
def test_third_component_only_required_in_3d(self):
556+
p2 = {**BASE_2D, **self.OUT, "bc_x%pres_out": 1.0, "bc_x%grcbc_vel_out": "T", "bc_x%vel_out(1)": 1.0, "bc_x%vel_out(2)": 0.0}
557+
self.assertAccepts(p2)
558+
p3 = {**p2, "p": 50, "bc_z%beg": -1, "bc_z%end": -1, "z_domain%beg": 0.0, "z_domain%end": 1.0, "patch_icpp(1)%z_centroid": 0.5, "patch_icpp(1)%length_z": 1.0, "patch_icpp(1)%vel(3)": 0.0}
559+
self.assertRejects(p3, "bc_x%vel_out(3) must be specified")

0 commit comments

Comments
 (0)