Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5b4999a
Add heterogeneous reacting surface boundary conditions
Sep 4, 2026
b6b2607
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 4, 2026
df96473
Fix GPU device linkage for surface thermochemistry
Sep 5, 2026
ceb5867
Fix GPU access to surface molecular weights
Sep 6, 2026
bd53b29
Fix OpenACC surface molecular weights
Sep 7, 2026
4ca778d
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 7, 2026
d3bd005
Fix surface chemistry OpenMP target and IBM species weights
Sep 7, 2026
0e37240
Fix OpenMP declare target syntax for surface thermochemistry
Sep 8, 2026
38af883
Match surface thermochemistry OpenMP directives
Sep 8, 2026
88a60ba
Merge master into carbon-surface-v1
sbryngelson Sep 8, 2026
58998e4
Add 2D heterogeneous reacting surface example
Sep 10, 2026
315676a
Merge remote-tracking branch 'upstream/master' into carbon-surface-v1
Sep 10, 2026
071c1b1
Add golden data for reacting surface example
Sep 10, 2026
f2e049a
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 10, 2026
791b323
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 11, 2026
d65f25a
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 11, 2026
a893ff6
Key a chemistry build on its mechanism, not the Cantera phase name
sbryngelson Sep 11, 2026
04bbcdf
Give the surface species arrays the same bound as the ones they are c…
sbryngelson Sep 11, 2026
380f20a
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 12, 2026
b81d521
Merge remote-tracking branch 'upstream/master' into HEAD
sbryngelson Sep 13, 2026
b096f5a
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 15, 2026
4649387
Fix review findings on reacting immersed-boundary surface chemistry (…
sbryngelson Sep 17, 2026
7fe89d4
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 17, 2026
eeaf6fb
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 17, 2026
e91c281
Shorten the cold-wall reacting-surface test to one step
sbryngelson Sep 18, 2026
10690ad
Unit-test the surface-mechanism guards in the chemistry toolchain
sbryngelson Sep 18, 2026
75f1a3b
Move the IB surface constraints to the validator, and lint the AMD sp…
sbryngelson Sep 18, 2026
5b1f28d
Drop the cold-wall test: its golden is not portable across compilers
sbryngelson Sep 18, 2026
ded92e3
Reduce the test suite to a single chemistry mechanism
sbryngelson Sep 19, 2026
e6e9c4a
Derive the Chemistry --only label from the case, not from its trace
sbryngelson Sep 19, 2026
cb691c6
Restore the reacting-surface golden; sandiego's slot pays for it
sbryngelson Sep 19, 2026
914662f
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 20, 2026
bc5198d
Correct the --only test docstring: American spelling, and the example…
sbryngelson Sep 20, 2026
0f77ddc
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 23, 2026
b92e549
Generate surface chemistry with the MFC thermochem package
sbryngelson Sep 23, 2026
720b08f
Merge branch 'master' into carbon-surface-v1
sbryngelson Sep 29, 2026
90dfe38
Regenerate the reacting-surface golden after #1792
sbryngelson Sep 30, 2026
9620f5b
Merge master into carbon-surface-v1
sbryngelson Sep 30, 2026
168eb13
Merge master into carbon-surface-v1
sbryngelson Sep 30, 2026
7e3ec6d
Merge master into carbon-surface-v1
sbryngelson Oct 1, 2026
de19134
Merge branch 'master' into carbon-surface-v1
sbryngelson Oct 5, 2026
683dd7b
Keep Twall on a failed surface solve, and tighten the surface validator
sbryngelson Oct 5, 2026
ec4a205
Add ib_surface_wrt: write wall temperature, gasified mass flux and he…
sbryngelson Oct 7, 2026
94855ec
Weight surface points over a two-cell band with a curvature correction
sbryngelson Oct 7, 2026
8456f5c
Write the IB surface file without a header line
sbryngelson Oct 7, 2026
06dbde0
Drop the heat-flux column from the IB surface output
sbryngelson Oct 7, 2026
ad7b980
Format
sbryngelson Oct 7, 2026
351b190
lint_docs: skip the ib_surface_wrt column names area and mdot
sbryngelson Oct 7, 2026
ec384a1
Merge remote-tracking branch 'origin/master' into ib-surface-flux
sbryngelson Oct 8, 2026
98c8fc9
Merge branch 'master' into ib-surface-flux
sbryngelson Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/documentation/case.md
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,7 @@ To restart the simulation from $k$-th time step, see @ref running "Restarting Ca
| `heat_ratio_wrt` | Logical | Add the specific heat ratio to the database |
| `ib_force_wrt` | Logical | Record the immersed-boundary force history to `D/ib_forces.dat` (default off) |
| `ib_force_stride` | Integer | Stride, in time steps, of the per-step immersed-boundary force record (default 1) |
| `ib_surface_wrt` | Logical | Write the wall temperature and gasified mass flux at each thermal/reacting IB surface point at every save (default off) |
| `ib_state_wrt` | Logical | Parameter to handle writing IB state on saves and outputting the state as a point mesh to SILO files. |
| `pi_inf_wrt` | Logical | Add the liquid stiffness function to the database |
| `pres_inf_wrt` | Logical | Add the liquid stiffness to the formatted database |
Expand Down Expand Up @@ -844,6 +845,8 @@ If `file_per_process` is true, then pre_process, simulation, and post_process mu

- `ib_force_wrt` records the force, torque and kinematics of every immersed boundary in a single shared text file, `D/ib_forces.dat`, described below. It is off by default: the history is written every step, which at large rank counts is a cost a run should opt into rather than inherit. `ib_force_stride` writes only every N-th step, for runs long enough that the history itself becomes large.

- `ib_surface_wrt` writes, at every save, one text file per rank, `D/ib_surface_<rank>_<save>.dat`, with a line per surface point of each chemistry IB (`thermal_bc` /= 0 or `surface_reaction` = 1) and no header, columns `x y z ib nx ny nz area T_wall mdot`. `ib` is the global IB index, `(nx, ny, nz)` the level-set normal, `T_wall` the surface temperature [K] and `mdot` the gasified mass flux [kg/m²/s] from the surface solve (0 on an inert surface). `area` [m², or m per unit depth in 2D] is the surface each point stands for: summing `area`·`mdot` over an IB's lines gives its mass loss rate [kg/s], and `mdot`/ρ_solid is the local surface regression rate. The points are the ghost points within two cell sizes h = (cell volume)^(1/d) of the surface, each weighted by cell volume/(2h); for circles, spheres and cylinder sides the weight is also scaled by (R/(R − depth))^(d−1), the area ratio between the surface and the layer the point sits in. The areas then sum to the surface area without bias (a sphere at R = 12h: within 0.3%; a circle at R = 20h: within about 1%); other shapes keep the uncorrected band, which underestimates a convex surface by about (d−1)·h/R.

#### Immersed-boundary force history {#sec-ib-force-history}

`D/ib_forces.dat` holds one fixed-width record per body per written step. Its twenty columns are
Expand Down
1 change: 1 addition & 0 deletions src/simulation/m_global_parameters.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ contains
ib_coefficient_of_friction = dflt_real
ib_state_wrt = .false.
ib_force_wrt = .false.
ib_surface_wrt = .false.
ib_force_stride = 1
many_ib_patch_parallelism = .false.

Expand Down
99 changes: 98 additions & 1 deletion src/simulation/m_ibm.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ module m_ibm

private :: s_compute_image_points, s_compute_interpolation_coeffs, s_interpolate_image_point, s_find_ghost_points, &
& s_find_num_ghost_points, s_compute_ghost_point_pressure, s_compute_ghost_point_velocity
; public :: s_initialize_ibm_module, s_ibm_setup, s_ibm_correct_state, s_finalize_ibm_module, s_report_ibm_surface
; public :: s_initialize_ibm_module, s_ibm_setup, s_ibm_correct_state, s_finalize_ibm_module, s_report_ibm_surface, &
& s_write_ib_surface

!> Ghost points at which the reacting-surface Newton solve did not reach its tolerance, so the point fell back to a chemically
!! inert wall (keeping a prescribed Twall). Counted because that fallback is otherwise indistinguishable from a surface
Expand All @@ -52,6 +53,13 @@ module m_ibm
type(ghost_point), dimension(:), allocatable :: ghost_points
$:GPU_DECLARE(create='[ghost_points]')

!> Surface record per ghost point from the latest s_ibm_correct_state, kept when ib_surface_wrt: (1) area weight, (2) wall
!! temperature, (3) gasified mass flux. Only ghost points within two cells of the surface carry a weight, so summing weight*flux
!! over them integrates over the surface; see s_record_gp_surface.
integer, parameter :: ib_surf_nvars = 3
real(wp), allocatable, dimension(:,:) :: gp_surf
$:GPU_DECLARE(create='[gp_surf]')

integer :: num_gps !< Number of ghost points
#if defined(MFC_OpenACC)
$:GPU_DECLARE(create='[gp_layers, num_gps]')
Expand Down Expand Up @@ -172,6 +180,11 @@ contains
@:ALLOCATE(ghost_points(1:max_num_gps))

$:GPU_ENTER_DATA(copyin='[ghost_points]')
if (ib_surface_wrt) then
@:ALLOCATE(gp_surf(ib_surf_nvars, 1:max_num_gps))
gp_surf = 0._wp
$:GPU_UPDATE(device='[gp_surf]')
end if
! Ghost-cell IBM, Tseng & Ferziger JCP (2003), Mittal & Iaccarino ARFM (2005)
call s_find_ghost_points()
call s_apply_levelset(ghost_points, num_gps)
Expand Down Expand Up @@ -349,6 +362,7 @@ contains
& surface_converged, vel_sum_g, E_ghost, alpha_q, alpha_rho_q, e_q]', &
& reduction='[[n_not_converged, n_ill_posed]]', reductionOp='[+]', present='[ghost_points]')
do i = 1, num_gps
if (ib_surface_wrt) gp_surf(:,i) = 0._wp
gp = ghost_points(i)
if (.not. gp%interp_valid) cycle
j = gp%loc(1)
Expand Down Expand Up @@ -436,6 +450,8 @@ contains
if (patch_ib(patch_id)%thermal_bc == 1) T_s = patch_ib(patch_id)%Twall
end if

if (ib_surface_wrt) call s_record_gp_surface(i, gp, T_s, mdot_s, surface_converged)

call s_blend_ghost_state(T_IP, T_s, Ys_IP, Ys_s, T_g, Ys_g)

call get_mixture_molecular_weight(Ys_g, mw_g)
Expand Down Expand Up @@ -704,6 +720,84 @@ contains

end subroutine s_report_ibm_surface

!> Store ghost point i's surface record in gp_surf. The weight is the surface area the point stands for. Ghost points within two
!! cell sizes h = dV^(1/d) of the surface fill a band of volume ~2hA, so dV/(2h) each sums to the area A (a length in 2D)
!! whatever the surface's orientation to the grid. The band lies inside the solid, where a layer at depth s has area A (1 -
!! s/R)^(d-1) on a curved surface; for circles, spheres and cylinders the weight is scaled back by (R/(R - s))^(d-1), which
!! removes that bias (sampled on random lattice offsets: < 0.1% mean, 0.3% spread for a sphere at R = 12h).
subroutine s_record_gp_surface(i, gp, T_s, mdot_s, reacting)

$:GPU_ROUTINE(parallelism='[seq]')

integer, intent(in) :: i
type(ghost_point), intent(in) :: gp
real(wp), intent(in) :: T_s, mdot_s
logical, intent(in) :: reacting
real(wp) :: d, dV, h, R

d = abs(real(gp%levelset, kind=wp))
dV = dx(gp%loc(1))*dy(gp%loc(2))
if (num_dims == 3) dV = dV*dz(gp%loc(3))
h = dV**(1._wp/real(num_dims, wp))
if (.not. (d > 0._wp .and. d <= 2._wp*h)) return

gp_surf(1, i) = dV/(2._wp*h)
R = patch_ib(gp%ib_patch_id)%radius
select case (patch_ib(gp%ib_patch_id)%geometry)
case (2, 8)
gp_surf(1, i) = gp_surf(1, i)*(R/(R - d))**(num_dims - 1)
case (10) ! curved side only; the flat caps need no correction
if (abs(gp%levelset_norm(f_cylinder_axis(patch_ib(gp%ib_patch_id)))) < 0.5_wp) gp_surf(1, i) = gp_surf(1, i)*R/(R - d)
end select

gp_surf(2, i) = T_s
gp_surf(3, i) = 0._wp
if (reacting) gp_surf(3, i) = mdot_s

end subroutine s_record_gp_surface

!> Axis (1, 2 or 3) of a cylinder IB: the one length that is set.
pure integer function f_cylinder_axis(ib_patch)

$:GPU_ROUTINE(parallelism='[seq]')

type(ib_patch_parameters), intent(in) :: ib_patch

f_cylinder_axis = 3
if (ib_patch%length_x > 0._wp) f_cylinder_axis = 1
if (ib_patch%length_y > 0._wp) f_cylinder_axis = 2

end function f_cylinder_axis

!> Write this rank's surface records to D/ib_surface_<rank>_<save>.dat, one line per weighted ghost point.
impure subroutine s_write_ib_surface(save_count)

integer, intent(in) :: save_count
character(LEN=path_len + 2*name_len) :: file_loc
real(wp) :: x(3)
integer :: i, unit

if (.not. allocated(gp_surf)) return

if (num_gps > 0) then
$:GPU_UPDATE(host='[gp_surf(:, 1:num_gps), ghost_points(1:num_gps)]')
end if

write (file_loc, '(A,I0,A,I0,A)') trim(case_dir) // '/D/ib_surface_', proc_rank, '_', save_count, '.dat'
open (newunit=unit, file=trim(file_loc), status='replace', action='write')
do i = 1, num_gps
if (.not. gp_surf(1, i) > 0._wp) cycle
x = 0._wp
x(1) = x_cc(ghost_points(i)%loc(1))
x(2) = y_cc(ghost_points(i)%loc(2))
if (num_dims == 3) x(3) = z_cc(ghost_points(i)%loc(3))
write (unit, '(3ES16.8,1X,I0,3ES15.6,3ES16.8)') x, patch_ib(ghost_points(i)%ib_patch_id)%gbl_patch_id, &
& ghost_points(i)%levelset_norm, gp_surf(:,i)
end do
close (unit)

end subroutine s_write_ib_surface

!> Compute the image points for each ghost point
impure subroutine s_compute_image_points()

Expand Down Expand Up @@ -2031,6 +2125,9 @@ contains
if (allocated(ghost_points)) then
@:DEALLOCATE(ghost_points)
end if
if (allocated(gp_surf)) then
@:DEALLOCATE(gp_surf)
end if
if (collision_model > 0) call s_finalize_collisions_module()
#ifdef MFC_MPI
if (num_procs > 1) then
Expand Down
1 change: 1 addition & 0 deletions src/simulation/m_start_up.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ contains

! Write IB kinematic state for restart
if (ib) call s_write_ib_state_file(save_count)
if (ib .and. ib_surface_wrt) call s_write_ib_surface(save_count)

call nvtxEndRange
call cpu_time(finish)
Expand Down
2 changes: 2 additions & 0 deletions toolchain/mfc/case_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,8 @@ def check_ibm(self):
self.prohibit(ib_state_wrt and not ib, "ib_state_wrt requires ib to be enabled")
ib_force_wrt = self.get("ib_force_wrt", False)
self.prohibit(ib_force_wrt and not ib, "ib_force_wrt requires ib to be enabled")
ib_surface_wrt = self.get("ib_surface_wrt", "F") == "T"
self.prohibit(ib_surface_wrt and not (ib and self.get("chemistry", "F") == "T"), "ib_surface_wrt requires ib and chemistry")
ib_force_stride = self.get("ib_force_stride", 1)
self.prohibit(ib_force_stride < 1, "ib_force_stride must be >= 1")

Expand Down
3 changes: 3 additions & 0 deletions toolchain/mfc/lint_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
"m_constants",
# Build/run target name (not a case param)
"pre_process",
# Output file column names (ib_surface_wrt)
"area",
"mdot",
}

# Docs to check for parameter references, with per-file skip sets
Expand Down
3 changes: 2 additions & 1 deletion toolchain/mfc/params/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ def _load():
_r("precision", INT, {"output"})
_r("format", INT, {"output"})
_r("ib_force_stride", INT, {"output", "ib"})
for n in ["parallel_io", "file_per_process", "run_time_info", "prim_vars_wrt", "cons_vars_wrt", "fft_wrt", "ib_state_wrt", "ib_force_wrt"]:
for n in ["parallel_io", "file_per_process", "run_time_info", "prim_vars_wrt", "cons_vars_wrt", "fft_wrt", "ib_state_wrt", "ib_force_wrt", "ib_surface_wrt"]:
_r(n, LOG, {"output"})
for n in [
"schlieren_wrt",
Expand Down Expand Up @@ -1356,6 +1356,7 @@ def _decl(targets: set, *names: str) -> None:
"ib_state_wrt",
"ib_force_wrt",
"ib_force_stride",
"ib_surface_wrt",
"avg_state",
"alt_soundspeed",
"mixture_err",
Expand Down
1 change: 1 addition & 0 deletions toolchain/mfc/params/descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
# Immersed boundaries
"ib": "Enable immersed boundary method",
"ib_force_wrt": "Record the immersed-boundary force history to D/ib_forces.dat (default off)",
"ib_surface_wrt": "Write per-surface-point wall temperature and gasified mass flux of thermal/reacting IBs to D/ib_surface_<rank>_<save>.dat (default off)",
"ib_force_stride": "Write the per-step immersed-boundary force record every N steps (default 1)",
"num_ibs": "Number of immersed boundary patches",
"num_stl_models": "Number of STL/OBJ model entries in the stl_models array",
Expand Down
4 changes: 4 additions & 0 deletions toolchain/mfc/test_case_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,10 @@ def test_twall_must_lie_in_the_tabulated_range(self):
self.assertRejects({**self.case(thermal_bc=1, Twall=9000.0), "chemistry": "T"}, "Twall must be within")
self.assertAccepts({**self.case(thermal_bc=1, Twall=210.0), "chemistry": "T"})

def test_surface_output_needs_chemistry(self):
self.assertRejects({**self.case(thermal_bc=1, Twall=1200.0), "ib_surface_wrt": "T"}, "ib_surface_wrt requires ib and chemistry")
self.assertAccepts({**self.case(thermal_bc=1, Twall=1200.0), "chemistry": "T", "ib_surface_wrt": "T"})

def test_the_energy_balance_needs_a_reacting_surface(self):
self.assertRejects({**self.case(thermal_bc=2), "chemistry": "T"}, "thermal_bc = 2 requires surface_reaction = 1")
self.assertAccepts({**self.case(thermal_bc=2, surface_reaction=1), "chemistry": "T", "surface_cantera_file": "s.yaml", "surface_phase": "surf"})
Expand Down
Loading