3.6 KiB
Fixed three-level separated patches
FixedPatchStepper supports exactly three static, blocked levels with separated
rectangle sets on either adjacent pair. This is not general AMR: there is no
per-region timestep, moving geometry, dynamic refinement or L1 body support.
All regions share one current/spare buffer pair per level. The existing schedule
advances L0 once, L1 twice and L2 four times per base step, with aggregate transfers.
No CUDA kernel changes are involved.
Explicit coordinate frames
Keep the existing scalar child=(x0,y0,x1,y1) for a single scalar parent patch.
Its coordinates retain their original meaning: half-open integer indices in the
allocated L1 window, including its ghost band.
The new keyword child_rectangles_in_parent=[...] is mutually exclusive with
child. It accepts a scalar or a set parent and interprets every rectangle in
the actual allocated L1 array, including ghost bands and holes. It never infers
region-local coordinates. The physical root cell edge corresponding to an L1
index (x,y) is parent.fine_origin + (x,y)/2:
- A scalar L1 parent has a shifted window origin.
- A set L1 parent has origin
(0,0)and public shape(2*ny,2*nx). - A child set has public shape twice the allocated L1 shape. Its root origin is the L1 allocated origin, even when the parent is a scalar window.
For a 16x16 root, scalar parent (4,4,12,12) and scalar child (8,8,16,16)
are physically equivalent to parent set [(4,4,12,12)] with child set
[(12,12,20,20)]. A child set on the scalar parent instead uses
[(8,8,16,16)]. Reusing the same numbers after changing the parent frame can
move the physical region.
Both adjacent pairs require a Chebyshev gap of at least four in their own parent-cell units. The whole child union must preserve inherited ACTIVE coverage, uncovered ACTIVE donors/receivers, surviving pulls, and the upstream restriction footprint. Sparse holes remain unowned; inherited INNER remains stream-only and OUTER remains invalid. Block sizes must align with every support edge, including its four-cell ghost band.
Example
run = FixedPatchStepper(
cfg, [(4, 4, 20, 28), (24, 4, 44, 28)], context=ctx,
base_block=(4, 4), fine_block=(4, 4), child_block=(4, 4),
child_rectangles_in_parent=[(16, 16, 32, 40), (56, 16, 80, 40)],
)
This example uses a 48x32 root. initialize and download use three Q-major
dense arrays. Public arrays and masks still scale with the full allocated
extent; sparse device buffers do not remove that host-memory limit.
Stationary whole-circle child_objects and child_jets use root edge
coordinates and a single L2 boundary object. Body IDs are physical object-list
positions, not region IDs or caller obj_id. Complete boundary stencils must
avoid the transfer footprint. Change a child Jet with
set_jet_amplitude(index, value, level=2); L1 bodies/Jets remain unsupported.
Verification
CPU geometry, table, pre-CUDA rejection, schedule and cleanup tests:
PYTHONPATH=src python -m unittest discover -s tests/unit -p 'test_refinement*.py' -v
Explicit GPU gates are in
tests/validation/multigrid/test_refinement_three_level_multipatch_gpu.py.
They cover physical-frame equivalence, twins, asymmetric refinement, multiple
children, reversed order, independently varied aligned layouts, public/raw
masks and two L2 bodies with static/zero/driven/switched Jets. Snapshots are
0/1/2/8. CPU mocks do not execute CUDA and do not establish numerical accuracy.
GPU equivalence gates supplement the separately frozen independent dense
three-level numerical replay; passing either alone is not a conservation,
performance or physical-accuracy claim.