Skip to content

Commit c3eac4c

Browse files
committed
feat(amr): moving bodies (prescribed motion) with dynamic regrid - the containment expansion reads the body's LIVE centroid (bbox helper factored from the expansion), the initial block gets the same body expansion when regridding is active, and a per-substage guard aborts if a moving body reaches its block boundary between regrids (consecutive contained positions keep every sub-time interpolate contained). Validated: 200-step tracking run (29 regrids, body rises 25 cells past its initial box, completion is the guard's own proof of following), guard-trip negative test aborts with the named message, error at/below the validated static-block control (rho L2 5.6e-3 vs 8.5e-3, identical body-surface Linf); Mach ~0.85 wakes legitimately exceed the per-rank box cap (named abort). Golden E72953D1 (Mach 0.25, 4 regrids)
1 parent 7717b05 commit c3eac4c

8 files changed

Lines changed: 313 additions & 50 deletions

File tree

docs/documentation/amr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ a diagnostic message for unsupported combinations.
216216
| QBMM bubbles (polytropic) | Supported | Bubble moments live in `q_cons`, injected piecewise-constant at prolongation to preserve CHyQMOM realizability |
217217
| QBMM bubbles (non-polytropic) | Supported (static block, no subcycle) | Each block carries its own `pb`/`mv` quadrature side-state: prolonged piecewise-constant (realizability), advanced with the block's own rhs scratch, restricted back with the moments; dynamic regrid and `amr_subcycle` remain gated |
218218
| Lagrangian bubbles | **Not supported** | Lagrangian tracking not advanced on the fine level |
219-
| Immersed boundaries (`ib = T`; one or more non-STL bodies, static or prescribed-motion `moving_ibm=1`) | Supported | Per-block fine-grid IB markers/ghost points, rebuilt each fine substage at the body's sub-time position for a moving body; non-conservative ghost-cell forcing at the body; with dynamic regrid (static bodies only) candidate boxes expand to fully contain each body plus margin and the fine IB state rebuilds after every regrid; force-driven (`moving_ibm=2`)/STL/moving-body-with-regrid gated; a body spanning a rank seam is rejected at startup |
219+
| Immersed boundaries (`ib = T`; one or more non-STL bodies, static or prescribed-motion `moving_ibm=1`) | Supported | Per-block fine-grid IB markers/ghost points, rebuilt each fine substage at the body's sub-time position for a moving body; non-conservative ghost-cell forcing at the body; with dynamic regrid candidate boxes expand to fully contain each body at its live position plus margin, the fine IB state rebuilds after every regrid, and a per-substage guard aborts if a moving body reaches its block boundary between regrids; force-driven (`moving_ibm=2`)/STL gated; a body spanning a rank seam is rejected at startup |
220220
| IGR solver | **Not supported** | Unvalidated with the fine-level advance |
221221
| 2D axisymmetric (`cyl_coord = T`, `p = 0`) | Supported | Geometric sources read the live (swapped) grid; the axis half-width cell's per-cell WENO coefficients are recomputed for each block on swap/restore; blocks stay `buff_size` off the axis (domain-edge clamp); the axis-singularity viscous treatment runs on the coarse pass only |
222222
| 3D cylindrical (`cyl_coord = T`, `p > 0`) | **Not supported** | The per-stage azimuthal Fourier filter is a global operation incompatible with the block-local fine advance |

docs/documentation/case.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -842,10 +842,11 @@ the flux reflux still conserves to machine precision away from it. A body in pre
842842
(`moving_ibm = 1`) is also supported: the fine block's IB markers/ghost points are rebuilt each fine
843843
RK substage at the body's sub-time position (the same linear time interpolation the subcycle applies
844844
to the fluid ghosts), so the refined body tracks its prescribed trajectory. Supports one or more
845-
non-STL bodies, static or in prescribed motion; with dynamic regrid (static bodies only) every
846-
candidate box expands to fully contain each body plus a margin and the fine IB state is rebuilt
847-
from geometry after each regrid. Force-driven motion (`moving_ibm = 2`), STL geometry, and moving
848-
bodies combined with dynamic regrid are gated pending validation. Under MPI a body contained within one rank's
845+
non-STL bodies, static or in prescribed motion; with dynamic regrid every candidate box expands
846+
to fully contain each body at its live position plus a margin, the fine IB state is rebuilt from
847+
geometry after each regrid, and a per-substage guard aborts if a moving body reaches its block
848+
boundary between regrids (reduce `amr_regrid_int` or increase `amr_buf`). Force-driven motion
849+
(`moving_ibm = 2`) and STL geometry are gated pending validation. Under MPI a body contained within one rank's
849850
subdomain is bit-exact across decompositions; a body spanning a rank seam is rejected at startup
850851
(the fine-IB image-point stencil across the seam is not yet decomposition-exact), so keep the body
851852
inside a single rank's subdomain (use fewer ranks or reposition it).

src/simulation/m_amr.fpp

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ contains
117117
integer :: i, d, max_f1, max_f2, max_f3, islot
118118
integer :: mbuf1_lo, mbuf1_hi, mbuf2_lo, mbuf2_hi, mbuf3_lo, mbuf3_hi
119119
integer :: sidx(3), ext(3), maxc_loc(3), bad_loc, bad_glb, fit_d
120+
integer :: blk_lo(3), blk_hi(3)
120121

121122
if (.not. amr) return
122123

@@ -290,8 +291,13 @@ contains
290291
! as q_cons so the fine IB pipeline can resolve the body on the block
291292
if (ib) call s_ibm_alloc_fine(amr_max_blocks, mbuf1_lo, mbuf1_hi, mbuf2_lo, mbuf2_hi, mbuf3_lo, mbuf3_hi)
292293

293-
! set geometry (region, m/n/p, idwbuff, coordinates) for the initial block (slot amr_cur = 1)
294-
call s_set_amr_fine_geometry(amr_block_beg, amr_block_end)
294+
! set geometry (region, m/n/p, idwbuff, coordinates) for the initial block (slot amr_cur = 1).
295+
! Under dynamic regrid with bodies the initial block gets the same body-containment
296+
! expansion regrid boxes get (the moving-body containment guard requires it from step 1);
297+
! for a static block (amr_regrid_int = 0) the user's placement is authoritative.
298+
blk_lo = amr_block_beg; blk_hi = amr_block_end
299+
if (ib .and. amr_regrid_int > 0) call s_amr_expand_box_over_bodies(blk_lo, blk_hi)
300+
call s_set_amr_fine_geometry(blk_lo, blk_hi)
295301
296302
end subroutine s_initialize_amr_module
297303
@@ -797,9 +803,38 @@ contains
797803
impure subroutine s_amr_update_mib_fine(th)
798804
799805
real(wp), intent(in) :: th
806+
integer :: i, blo(3), bhi(3)
807+
logical :: ovl, inside
800808
801809
if (.not. ib) return
802810
if (.not. amr_rank_owns_block) return
811+
! Between regrids a moving body must stay inside its block: the regrid expansion
812+
! contained it with margin max(amr_buf,4), and here we require the body plus the
813+
! image-point stencil reach (2 coarse cells) to remain contained. Consecutive
814+
! contained positions keep every sub-time interpolate contained (axis-aligned
815+
! boxes are convex in the linearly-moving corners). A body that overlaps the
816+
! block edge would get silently clipped forcing - abort instead.
817+
if (amr_regrid_int > 0) then
818+
do i = 1, num_ibs
819+
if (patch_ib(i)%moving_ibm == 0) cycle
820+
call s_amr_body_bbox(i, 2, blo, bhi)
821+
ovl = blo(1) <= amr_slots(amr_cur)%region%hi(1) .and. bhi(1) >= amr_slots(amr_cur)%region%lo(1)
822+
if (n_glb > 0) ovl = ovl .and. blo(2) <= amr_slots(amr_cur)%region%hi(2) .and. bhi(2) &
823+
& >= amr_slots(amr_cur)%region%lo(2)
824+
if (p_glb > 0) ovl = ovl .and. blo(3) <= amr_slots(amr_cur)%region%hi(3) .and. bhi(3) &
825+
& >= amr_slots(amr_cur)%region%lo(3)
826+
if (.not. ovl) cycle
827+
inside = blo(1) >= amr_slots(amr_cur)%region%lo(1) .and. bhi(1) <= amr_slots(amr_cur)%region%hi(1)
828+
if (n_glb > 0) inside = inside .and. blo(2) >= amr_slots(amr_cur)%region%lo(2) .and. bhi(2) &
829+
& <= amr_slots(amr_cur)%region%hi(2)
830+
if (p_glb > 0) inside = inside .and. blo(3) >= amr_slots(amr_cur)%region%lo(3) .and. bhi(3) &
831+
& <= amr_slots(amr_cur)%region%hi(3)
832+
if (.not. inside) then
833+
call s_mpi_abort('amr dynamic regrid with moving ib: the body reached the fine-block ' &
834+
& // 'boundary between regrids; reduce amr_regrid_int or increase amr_buf')
835+
end if
836+
end do
837+
end if
803838
call s_amr_swap_to_fine()
804839
call s_ibm_swap_to_fine(amr_cur, gps_on_device=.true.)
805840
call s_update_mib(num_ibs, th)
@@ -1870,13 +1905,51 @@ contains
18701905
18711906
!> Expand a candidate regrid box (global indices) to fully contain every immersed body it overlaps, with a buff_size margin (the
18721907
!! IB image-point stencils need resolved surroundings). Expansion is re-clamped to the domain interior by the caller's own
1873-
!! guards; a body too large for the per-rank block cap aborts with a named message. Static bodies only (moving bodies with
1874-
!! dynamic regrid remain gated).
1908+
!! guards; a body too large for the per-rank block cap aborts with a named message. The bbox reads the live centroid, so a
1909+
!! moving body's box tracks its current position; between regrids s_amr_update_mib_fine guards containment.
1910+
!> Margin-padded global coarse-index bounding box of immersed body i (supported analytic geometries only; aborts on others).
1911+
!! Reads the body's LIVE centroid, so a moving body's box tracks its current position.
1912+
impure subroutine s_amr_body_bbox(i, mrg, blo, bhi)
1913+
1914+
integer, intent(in) :: i, mrg
1915+
integer, intent(out) :: blo(3), bhi(3)
1916+
real(wp) :: c(3), half(3)
1917+
1918+
c = [patch_ib(i)%x_centroid, patch_ib(i)%y_centroid, patch_ib(i)%z_centroid]
1919+
select case (patch_ib(i)%geometry)
1920+
case (2, 8, 10) ! circle, sphere, cylinder: radius-bounded (cylinder length adds below)
1921+
half = patch_ib(i)%radius
1922+
if (patch_ib(i)%geometry == 10) then
1923+
half(1) = max(half(1), 0.5_wp*patch_ib(i)%length_x)
1924+
half(2) = max(half(2), 0.5_wp*patch_ib(i)%length_y)
1925+
half(3) = max(half(3), 0.5_wp*patch_ib(i)%length_z)
1926+
end if
1927+
case (3, 9) ! rectangle, box
1928+
half = 0.5_wp*[patch_ib(i)%length_x, patch_ib(i)%length_y, patch_ib(i)%length_z]
1929+
case default
1930+
call s_mpi_abort('amr dynamic regrid with ib: unsupported body geometry for the ' &
1931+
& // 'containment bounding box (supported: circle/rectangle/sphere/box/cylinder)')
1932+
end select
1933+
! physical bbox -> global coarse indices (uniform spacing is enforced for amr; the
1934+
! axisymmetric half axis cell only shrinks dy(0), so the floor is still conservative)
1935+
blo(1) = int((c(1) - half(1) - x_domain%beg)/dx(0)) - mrg
1936+
bhi(1) = int((c(1) + half(1) - x_domain%beg)/dx(0)) + mrg
1937+
blo(2) = 0; bhi(2) = 0; blo(3) = 0; bhi(3) = 0
1938+
if (n_glb > 0) then
1939+
blo(2) = int((c(2) - half(2) - y_domain%beg)/dy(min(1, n))) - mrg
1940+
bhi(2) = int((c(2) + half(2) - y_domain%beg)/dy(min(1, n))) + mrg
1941+
end if
1942+
if (p_glb > 0) then
1943+
blo(3) = int((c(3) - half(3) - z_domain%beg)/dz(0)) - mrg
1944+
bhi(3) = int((c(3) + half(3) - z_domain%beg)/dz(0)) + mrg
1945+
end if
1946+
1947+
end subroutine s_amr_body_bbox
1948+
18751949
impure subroutine s_amr_expand_box_over_bodies(lo, hi)
18761950
18771951
integer, intent(inout) :: lo(3), hi(3)
18781952
integer :: i, d, blo(3), bhi(3), mrg
1879-
real(wp) :: c(3), half(3)
18801953
logical :: ovl
18811954
18821955
! containment margin: the IB image-point stencil reaches a few cells beyond the surface
@@ -1886,34 +1959,7 @@ contains
18861959
mrg = max(amr_buf, 4)
18871960
18881961
do i = 1, num_ibs
1889-
c = [patch_ib(i)%x_centroid, patch_ib(i)%y_centroid, patch_ib(i)%z_centroid]
1890-
select case (patch_ib(i)%geometry)
1891-
case (2, 8, 10) ! circle, sphere, cylinder: radius-bounded (cylinder length adds below)
1892-
half = patch_ib(i)%radius
1893-
if (patch_ib(i)%geometry == 10) then
1894-
half(1) = max(half(1), 0.5_wp*patch_ib(i)%length_x)
1895-
half(2) = max(half(2), 0.5_wp*patch_ib(i)%length_y)
1896-
half(3) = max(half(3), 0.5_wp*patch_ib(i)%length_z)
1897-
end if
1898-
case (3, 9) ! rectangle, box
1899-
half = 0.5_wp*[patch_ib(i)%length_x, patch_ib(i)%length_y, patch_ib(i)%length_z]
1900-
case default
1901-
call s_mpi_abort('amr dynamic regrid with ib: unsupported body geometry for the ' &
1902-
& // 'containment bounding box (supported: circle/rectangle/sphere/box/cylinder)')
1903-
end select
1904-
! physical bbox -> global coarse indices (uniform spacing is enforced for amr; the
1905-
! axisymmetric half axis cell only shrinks dy(0), so the floor is still conservative)
1906-
blo(1) = int((c(1) - half(1) - x_domain%beg)/dx(0)) - mrg
1907-
bhi(1) = int((c(1) + half(1) - x_domain%beg)/dx(0)) + mrg
1908-
blo(2) = 0; bhi(2) = 0; blo(3) = 0; bhi(3) = 0
1909-
if (n_glb > 0) then
1910-
blo(2) = int((c(2) - half(2) - y_domain%beg)/dy(min(1, n))) - mrg
1911-
bhi(2) = int((c(2) + half(2) - y_domain%beg)/dy(min(1, n))) + mrg
1912-
end if
1913-
if (p_glb > 0) then
1914-
blo(3) = int((c(3) - half(3) - z_domain%beg)/dz(0)) - mrg
1915-
bhi(3) = int((c(3) + half(3) - z_domain%beg)/dz(0)) + mrg
1916-
end if
1962+
call s_amr_body_bbox(i, mrg, blo, bhi)
19171963
! blocks must stay buff_size inside the domain: a body whose margin-padded bbox does
19181964
! not fit cannot be contained - fail with a named message instead of a clipped body
19191965
if (blo(1) < buff_size .or. bhi(1) > m_glb - buff_size .or. (n_glb > 0 .and. (blo(2) < buff_size .or. bhi(2) > n_glb &

src/simulation/m_checker.fpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,16 @@ contains
129129
! static-body IB AMR (SP20) + prescribed-motion moving bodies (SP21): fixed or analytically-moving
130130
! (moving_ibm==1) bodies resolved on a static fine block. Multi-body (num_ibs>1) is supported - every body
131131
! shares the one static block and reuses the multi-body-capable core IB setup. Force/torque-driven motion
132-
! (moving_ibm==2), STL geometry, and dynamic regrid with IB remain gated (unvalidated / recompute-on-move).
132+
! (moving_ibm==2) and STL geometry remain gated (unvalidated).
133133
@:PROHIBIT(ib .and. any(patch_ib(1:num_ibs)%moving_ibm == 2), &
134134
& "amr with ib supports static or prescribed-motion (moving_ibm=1) bodies only; force-driven moving IB (moving_ibm=2) under amr is not yet validated")
135135
@:PROHIBIT(ib .and. any(patch_ib(1:num_ibs)%geometry == 12), &
136136
& "amr with ib does not support STL-model geometry (not yet validated)")
137-
! dynamic regrid with STATIC bodies: candidate boxes expand to fully contain every body
138-
! (partial coverage is an untested regime), overlapping expansions merge, and the fine
139-
! IB state is rebuilt from the geometry after every regrid. Moving bodies stay gated:
140-
! their per-substep position updates are not yet coupled to a moving block set.
141-
@:PROHIBIT(ib .and. amr_regrid_int > 0 .and. any(patch_ib(1:num_ibs)%moving_ibm > 0), &
142-
& "amr dynamic regrid with ib supports static bodies only; moving bodies require a static block (amr_regrid_int = 0)")
137+
! dynamic regrid with bodies (static or prescribed-motion): candidate boxes expand to
138+
! fully contain every body at its LIVE position (partial coverage is an untested
139+
! regime), overlapping expansions merge, and the fine IB state is rebuilt from the
140+
! geometry after every regrid. Between regrids a moving body's containment is
141+
! guarded per substage (abort if it reaches the block boundary).
143142
@:PROHIBIT(active_box, "amr is incompatible with active_box (unvalidated combination)")
144143
! no hybrid_weno/hybrid_riemann gate: the sensor arrays are sized to the coarse
145144
! idwbuff (the fine extent guard keeps fine bounds inside them) and the sensor is

0 commit comments

Comments
 (0)