diff --git a/README.md b/README.md index 7a59a88..171beea 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,9 @@ with CudaContext(0) as ctx, FixedPatchStepper(cfg, (8, 6, 24, 18), context=ctx, ``` Scope and limits: -- D2Q9, SRT, FP32, double buffer, unshifted, no LES or forcing; fixed 2:1 patch(es), two levels (or one nested third). +- The examples below use D2Q9, SRT, FP32, double buffer, unshifted, no LES or + forcing. Fixed 2:1 hierarchies can use level lists of arbitrary depth; the + legacy two/three-level forms are shown below. - `coarse_boundary="periodic"` (default) or `"slot_reservoir"` (bottom slot, top reservoir; patch needs `y0 >= 4`, `y1 <= ny-4`). Periodic mode ignores the wall/slot config fields. - Object centers/radii use the coarse **cell-edge** frame (cell `I` spans `[I, I+1)`), @@ -135,16 +137,20 @@ Scope and limits: divide the WHOLE fine allocation, i.e. `2*(y1-y0)+8` by `2*(x1-x0)+8` including the four-cell ghost band. It gives results identical to dense storage and is infrastructure for future block AMR, not a speedup. -- Several separated patches: pass a sequence of `(x0, y0, x1, y1)` - rectangles as `bounds`, at least 4 coarse cells apart, with both `fine_block` - and `base_block`. The fine level is then one level-wide grid `(2*ny, 2*nx)`: +- Several regions: pass a sequence of `(x0, y0, x1, y1)` rectangles as + `bounds`, with both `fine_block` and `base_block`. Transfers are built once + from their union; adjacent, overlapping and concave unions are accepted when + their actual dependencies close. There is no fixed rectangle-gap rule. + The fine level is then one level-wide grid `(2*ny, 2*nx)`: `run.patch.fine_shape` gives it, `initialize`/`download` use it, and only the - patch windows (active region plus 4-cell ghost band, origin `(2*y0-4, 2*x0-4)`) - are stored; each window must align with `fine_block`. Order of rectangles does - not matter. `fine_objects`/`jets` work as for one patch: each circle (same - coarse cell-edge frame) must lie wholly inside one patch's active region with + grid-aligned tiles intersecting the union plus its four-cell ghost band are stored. + Block sizes must divide the level-wide grid; rectangle edges need not align + with tiles. Order of rectangles does not matter. `fine_objects`/`jets` work + as for one patch: each circle (same coarse cell-edge frame) must lie wholly + inside the union's active region with its boundary stencil clear of transfer cells; a jet may not select links of two - different bodies, even across patches. No wall-touching patches or nesting yet. + different bodies, even across regions. Wall-touching patches remain unsupported. + For nesting, use the level lists or legacy child forms below. ```python cfg = LBMConfig(nx=32, ny=16, viscosity=.1) @@ -158,7 +164,7 @@ Scope and limits: coarse_g, fine_g = run.download() # coarse: NaN on every covered cell; # fine: NaN on every non-ACTIVE cell ``` -- Three levels (one nested chain): add `child=(x0, y0, x1, y1)` in +- Legacy three-level scalar chain: add `child=(x0, y0, x1, y1)` in **L1 window cells** (the patch's fine grid including its 4-cell ghost band) and `child_block`, with `fine_block` and `base_block`. L2 must sit inside L1's ACTIVE cells, away from L1's ghost band and transfer footprint (checked before @@ -192,14 +198,30 @@ Scope and limits: run.step(4) coarse_g, l1_g, l2_g = run.download() # solids/covered/nonleaves are NaN ``` -- Not available: per-link surface exchange/force readout in blocked mode, - checkpoints, moving bodies, L1 bodies/Jets in a three-level chain, - several patches per level with nesting, dynamic refinement, `Simulation` - integration. Validation so far is storage/operator equivalence and short runs, - not accuracy, convergence or long-time stability. The new L2-body GPU gates - (layouts, masks, amplitude reversal, legacy equivalence) and independent - every-substep numerical replay remain required acceptance checks; CPU tests - alone do not establish GPU numerical correctness. +- Level lists: `refinements[l]` describes level `l+1` in allocated level-`l` + integer cells; use a rectangle list for a union, or a scalar rectangle for a + single window (a scalar child requires a scalar parent). `level_blocks`, + `level_objects` and `level_jets` each have one entry per level, including L0. + These lists are mutually exclusive with the legacy patch keywords. Three or + more levels require all-blocked storage and bodies/Jets only on the finest + level. Every adjacent pair must preserve inherited ACTIVE coverage, valid + donors/receivers, surviving pulls and the upstream transfer footprint. + Level `l` takes `2**l` steps per base interval. This describes implemented + topology and scheduling, not accuracy or convergence qualification. +- Blocked body levels can enable `observe_raw_exchange=True` and call + `read_raw_exchange(body_id, level=...)` after a completed base interval. + It reduces unique cut-link observations into wall/aperture/total force, + torque and mass exchange in base-lattice units per unit depth, for the last + base interval (default) or `window="last_substep"`. + `observe_window_exchange=True`, alongside `observe_raw_exchange=True`, enables + `start_exchange_window()` / `read_exchange_window(body_id, level=...)` across + completed base intervals. These are raw boundary-reaction diagnostics, not a general + stress or nozzle-load accuracy claim; dense body layouts are unsupported. +- Not available: checkpoints, moving bodies, L1 bodies/Jets in a three-level + chain, automatic dynamic refinement, `Simulation` integration. Qualification + depends on the tested configuration; see [the validation scope](docs/validation/2d_scope.md). + CPU topology/schedule tests alone do not establish GPU numerical correctness, + general convergence or long-time stability. ## Installation diff --git a/docs/fixed_three_level_multipatch.md b/docs/fixed_three_level_multipatch.md index b8190dd..610890d 100644 --- a/docs/fixed_three_level_multipatch.md +++ b/docs/fixed_three_level_multipatch.md @@ -1,8 +1,11 @@ -# Fixed three-level separated patches +# Fixed three-level rectangle unions -`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. +This page describes the legacy three-level entry point of `FixedPatchStepper`, +with rectangle unions on either adjacent pair. The `refinements` level-list +entry point also supports arbitrary depth; three or more levels require +all-blocked storage and bodies/Jets only on the finest level. This is not +general AMR: there is no per-region timestep, moving geometry, automatic +dynamic refinement or L1 body support in the three-level form. 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. @@ -30,12 +33,15 @@ are physically equivalent to parent set `[(4,4,12,12)]` with child set `[(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 +Both adjacent pairs are built from their unions, without a fixed rectangle-gap +rule. Adjacent, overlapping and concave unions are accepted only when their +actual transfer dependencies close. 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. +stream-only and OUTER remains invalid. Block sizes must divide their allocated +level grids. Sparse storage owns every grid-aligned tile intersecting the union +plus its four-cell ghost band; rectangle and support edges need not align with +tile edges. ## Example @@ -65,6 +71,12 @@ CPU geometry, table, pre-CUDA rejection, schedule and cleanup tests: PYTHONPATH=src python -m unittest discover -s tests/unit -p 'test_refinement*.py' -v ``` +`tests/unit/test_union_refinement.py` covers union dependencies and tile +ownership; `tests/unit/test_hierarchy.py` covers composed geometry through four +levels. `tests/unit/test_raw_exchange.py` checks the four-level host schedule +with CUDA mocks. These establish topology/control-flow coverage, not numerical +qualification at every depth or interface shape. + Explicit GPU gates are in `tests/validation/multigrid/test_refinement_three_level_multipatch_gpu.py`. They cover physical-frame equivalence, twins, asymmetric refinement, multiple diff --git a/docs/synchronized_leaf_remap.md b/docs/synchronized_leaf_remap.md index 639cf53..7a1bcd5 100644 --- a/docs/synchronized_leaf_remap.md +++ b/docs/synchronized_leaf_remap.md @@ -1,11 +1,15 @@ # Synchronized leaf G remap and manual continuation This is a CPU state-transfer component, **not a runtime adaptive solver**. -It changes only a separated L2 rectangle set in a three-level, body-free, -periodic hierarchy. L0/L1 topology, relaxation parameters, direction order, +It changes only an L2 rectangle union in a three-level periodic hierarchy. +The body-free case is shown first; stationary L2 bodies/Jets follow the bounded +support contract below. L0/L1 topology, relaxation parameters, direction order, physical frame and completed time remain unchanged. Existing geometry checks -validate child support/nesting and separation. The CPU helper itself adds no CUDA, scheduler, allocator, boundary handling, -collision or ghost refill. A bounded runtime adapter is described below. +validate child support/nesting and actual transfer dependencies, without a fixed +rectangle-gap rule. The three-level remap limit is unchanged by the stepper's +arbitrary-depth level-list support. The CPU helper itself adds no CUDA, +scheduler, allocator, boundary handling, collision or ghost refill. A bounded +runtime adapter is described below. ```python from CelerisLab.lbm.refinement import FixedPatchSet2D @@ -29,11 +33,13 @@ origin is retained. Physical lower edges are `origin + spacing*(x,y)` on L0, `origin + spacing*(parent.fine_origin + (x,y)/4)` on L2. Factory options `coarse_boundary` and `objects` must declare `"periodic"` and -an empty body collection (all levels). LBMConfig's channel settings do not -select this operator's boundary mode. Geometry metadata is factory-created; +an empty collection; L2 bodies use `child_objects` as described below. +LBMConfig's channel settings do not select this operator's boundary mode. +Geometry metadata is factory-created; manual mutation/forging of metadata is unsupported. Caller certifies the -actual state has no bodies/forcing, that all device work and old readers are -quiescent, and that inputs are the **current owned-leaf postcollision G**. +actual state matches the declared boundary and has no forcing, that all device +work and old readers are quiescent, and that inputs are the **current owned-leaf +postcollision G**. Count/phase stamps check consistency, not freshness or active-buffer identity. `current_g` is three NumPy Q-major arrays `(9,ny,nx)`. For blocked inputs, @@ -73,9 +79,10 @@ PYTHONPATH=src python -m unittest discover -s tests/unit -p 'test_synchronized_r `replacement = current.remap_child(rectangles)` returns a new ready `FixedPatchStepper` at the **same absolute step_count**. It supports only -body-free periodic three-level runs whose existing L2 uses -`child_rectangles_in_parent`. Scalar or union L1 parents are supported; scalar -`child=` is deliberately rejected. L0/L1, relaxation and block sizes stay fixed. +periodic three-level runs with no L0/L1 bodies and an existing L2 +`FixedPatchSet2D`; stationary L2 bodies/Jets follow the support contract below. +Scalar or union L1 parents are supported; scalar `child=` is deliberately +rejected. L0/L1, relaxation and block sizes stay fixed. No tagger, scheduler, in-place replacement, or general rollback is provided. The method holds the stepper lock, validates geometry, synchronizes its stream, @@ -113,13 +120,14 @@ Per-population conservation is not an advancement target under collision. ### Bounded pinned-body extension `replacement = current.remap_child(rectangles, child_objects=original_objects)` -also accepts stationary Circles in separated L2 rectangles. Every body’s -**complete containing rectangle must remain identical**. Several bodies may -share one pinned rectangle; there need not be any fluid-only rectangles. -Separated fluid-only rectangles may be added, removed or moved. Existing -nesting, four-cell gap, full-circle containment, reverse cut-link coverage and two-hop BFL -transfer-footprint checks still apply. This is manual remapping, not adaptive -body tracking or runtime body editing. +also accepts stationary Circles inside the L2 union. Each body's **physical +boundary, complete BFL support and current per-link velocity must remain +identical**. Rectangles are not pinned: they may be split, merged or resized, +and other fluid coverage may change, including within the same connected union, +provided that physical support is unchanged. Existing nesting, union transfer +dependencies, full-circle containment, reverse cut-link +coverage and two-hop BFL transfer-footprint checks still apply. This is manual +remapping, not adaptive body tracking or runtime body editing. Supply all original root edge-frame definitions in their construction order. Do not pass `current.child_boundary.objects`: those are projected fine-local copies. @@ -128,8 +136,8 @@ state, remapping reprojects the supplied definition onto the old topology and compares exact ordered circle centers/radii, flags, physical cut links, body IDs, q, lever arms, hits, BFL support, legacy SurfaceActuation specification and current per-link velocity against -the existing boundary. Omission, changed geometry/actuation and changed body -rectangle fail without device reads or allocations; reordered bodies fail +the existing boundary. Omission, changed geometry/actuation or changed physical +boundary/BFL support fail without device reads or allocations; reordered bodies fail too. Root object IDs are still assigned by list position, as in the constructor. Jets are taken from the live L2 boundary, including the most recent successful @@ -141,12 +149,16 @@ boundary and velocities must match exactly. typed `FineBoundary2D`; it derives L2 fluid leaves as ACTIVE minus SOLID. No caller mask is accepted. Solids, ghosts and holes are never donors and remain NaN, physical-fluid overlap is exact, and exposed L1 still restricts all four -old fluid children. The helper checks the old/new body pins and boundary too. -Its immutable `pinned_rectangles` is a canonical, deduplicated tuple, empty for body-free layouts; rectangle input -order does not affect it. Scalar-parent window offsets remain part of the -coordinate conversion. +old fluid children. The helper checks old/new physical boundary and velocity +equality too. Its immutable `pinned_rectangles` is legacy containment metadata, +not a remap eligibility constraint; it may be empty for a seam-spanning body. +Scalar-parent window offsets remain part of the coordinate conversion. -Focused CPU mocks: `tests/unit/test_pinned_body_remap.py`. GPU declarations: +Focused CPU mocks: `tests/unit/test_pinned_body_remap.py`. +`tests/unit/test_union_body_support.py` also checks seam-spanning circles, +rectangle repartition/resize, unchanged physical support and live-Jet +preservation. These host checks do not establish GPU evolution accuracy. +GPU declarations: `tests/validation/multigrid/test_pinned_body_remap_gpu.py`, covering no-op next-step bitwise identity, moved-fluid A→B→A, current-amplitude preservation, physical boundary/velocity equality, source independence and absolute counts.