docs: correct refinement topology and diagnostics scope

This commit is contained in:
Codex committed 2026-10-18 13:13:53 -07:00
1 parent fc0e544c0a
commit ed9d8c937d
3 files changed
+98 -52

No files matched your search

+40 -18
View File
@@ -123,7 +123,9 @@ with CudaContext(0) as ctx, FixedPatchStepper(cfg, (8, 6, 24, 18), context=ctx,
``` ```
Scope and limits: 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 - `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. 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)`), - 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 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 four-cell ghost band. It gives results identical to dense storage and is
infrastructure for future block AMR, not a speedup. infrastructure for future block AMR, not a speedup.
- Several separated patches: pass a sequence of `(x0, y0, x1, y1)` - Several regions: pass a sequence of `(x0, y0, x1, y1)` rectangles as
rectangles as `bounds`, at least 4 coarse cells apart, with both `fine_block` `bounds`, with both `fine_block` and `base_block`. Transfers are built once
and `base_block`. The fine level is then one level-wide grid `(2*ny, 2*nx)`: 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 `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)`) grid-aligned tiles intersecting the union plus its four-cell ghost band are stored.
are stored; each window must align with `fine_block`. Order of rectangles does Block sizes must divide the level-wide grid; rectangle edges need not align
not matter. `fine_objects`/`jets` work as for one patch: each circle (same with tiles. Order of rectangles does not matter. `fine_objects`/`jets` work
coarse cell-edge frame) must lie wholly inside one patch's active region with 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 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 ```python
cfg = LBMConfig(nx=32, ny=16, viscosity=.1) 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; coarse_g, fine_g = run.download() # coarse: NaN on every covered cell;
# fine: NaN on every non-ACTIVE 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 **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 `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 ACTIVE cells, away from L1's ghost band and transfer footprint (checked before
@@ -192,14 +198,30 @@ Scope and limits:
run.step(4) run.step(4)
coarse_g, l1_g, l2_g = run.download() # solids/covered/nonleaves are NaN coarse_g, l1_g, l2_g = run.download() # solids/covered/nonleaves are NaN
``` ```
- Not available: per-link surface exchange/force readout in blocked mode, - Level lists: `refinements[l]` describes level `l+1` in allocated level-`l`
checkpoints, moving bodies, L1 bodies/Jets in a three-level chain, integer cells; use a rectangle list for a union, or a scalar rectangle for a
several patches per level with nesting, dynamic refinement, `Simulation` single window (a scalar child requires a scalar parent). `level_blocks`,
integration. Validation so far is storage/operator equivalence and short runs, `level_objects` and `level_jets` each have one entry per level, including L0.
not accuracy, convergence or long-time stability. The new L2-body GPU gates These lists are mutually exclusive with the legacy patch keywords. Three or
(layouts, masks, amplitude reversal, legacy equivalence) and independent more levels require all-blocked storage and bodies/Jets only on the finest
every-substep numerical replay remain required acceptance checks; CPU tests level. Every adjacent pair must preserve inherited ACTIVE coverage, valid
alone do not establish GPU numerical correctness. 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 ## Installation
+21 -9
View File
@@ -1,8 +1,11 @@
# Fixed three-level separated patches # Fixed three-level rectangle unions
`FixedPatchStepper` supports exactly three static, blocked levels with separated This page describes the legacy three-level entry point of `FixedPatchStepper`,
rectangle sets on either adjacent pair. This is not general AMR: there is no with rectangle unions on either adjacent pair. The `refinements` level-list
per-region timestep, moving geometry, dynamic refinement or L1 body support. 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 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. advances L0 once, L1 twice and L2 four times per base step, with aggregate transfers.
No CUDA kernel changes are involved. 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 `[(8,8,16,16)]`. Reusing the same numbers after changing the parent frame can
move the physical region. move the physical region.
Both adjacent pairs require a Chebyshev gap of at least four in their own Both adjacent pairs are built from their unions, without a fixed rectangle-gap
parent-cell units. The whole child union must preserve inherited ACTIVE rule. Adjacent, overlapping and concave unions are accepted only when their
coverage, uncovered ACTIVE donors/receivers, surviving pulls, and the upstream 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 restriction footprint. Sparse holes remain unowned; inherited INNER remains
stream-only and OUTER remains invalid. Block sizes must align with every stream-only and OUTER remains invalid. Block sizes must divide their allocated
support edge, including its four-cell ghost band. 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 ## 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 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 Explicit GPU gates are in
`tests/validation/multigrid/test_refinement_three_level_multipatch_gpu.py`. `tests/validation/multigrid/test_refinement_three_level_multipatch_gpu.py`.
They cover physical-frame equivalence, twins, asymmetric refinement, multiple They cover physical-frame equivalence, twins, asymmetric refinement, multiple
+37 -25
View File
@@ -1,11 +1,15 @@
# Synchronized leaf G remap and manual continuation # Synchronized leaf G remap and manual continuation
This is a CPU state-transfer component, **not a runtime adaptive solver**. 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, It changes only an L2 rectangle union in a three-level periodic hierarchy.
periodic hierarchy. L0/L1 topology, relaxation parameters, direction order, 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 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, validate child support/nesting and actual transfer dependencies, without a fixed
collision or ghost refill. A bounded runtime adapter is described below. 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 ```python
from CelerisLab.lbm.refinement import FixedPatchSet2D 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. `origin + spacing*(parent.fine_origin + (x,y)/4)` on L2.
Factory options `coarse_boundary` and `objects` must declare `"periodic"` and Factory options `coarse_boundary` and `objects` must declare `"periodic"` and
an empty body collection (all levels). LBMConfig's channel settings do not an empty collection; L2 bodies use `child_objects` as described below.
select this operator's boundary mode. Geometry metadata is factory-created; 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 manual mutation/forging of metadata is unsupported. Caller certifies the
actual state has no bodies/forcing, that all device work and old readers are actual state matches the declared boundary and has no forcing, that all device
quiescent, and that inputs are the **current owned-leaf postcollision G**. 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. 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, `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 `replacement = current.remap_child(rectangles)` returns a new ready
`FixedPatchStepper` at the **same absolute step_count**. It supports only `FixedPatchStepper` at the **same absolute step_count**. It supports only
body-free periodic three-level runs whose existing L2 uses periodic three-level runs with no L0/L1 bodies and an existing L2
`child_rectangles_in_parent`. Scalar or union L1 parents are supported; scalar `FixedPatchSet2D`; stationary L2 bodies/Jets follow the support contract below.
`child=` is deliberately rejected. L0/L1, relaxation and block sizes stay fixed. 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. No tagger, scheduler, in-place replacement, or general rollback is provided.
The method holds the stepper lock, validates geometry, synchronizes its stream, 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 ### Bounded pinned-body extension
`replacement = current.remap_child(rectangles, child_objects=original_objects)` `replacement = current.remap_child(rectangles, child_objects=original_objects)`
also accepts stationary Circles in separated L2 rectangles. Every body’s also accepts stationary Circles inside the L2 union. Each body's **physical
**complete containing rectangle must remain identical**. Several bodies may boundary, complete BFL support and current per-link velocity must remain
share one pinned rectangle; there need not be any fluid-only rectangles. identical**. Rectangles are not pinned: they may be split, merged or resized,
Separated fluid-only rectangles may be added, removed or moved. Existing and other fluid coverage may change, including within the same connected union,
nesting, four-cell gap, full-circle containment, reverse cut-link coverage and two-hop BFL provided that physical support is unchanged. Existing nesting, union transfer
transfer-footprint checks still apply. This is manual remapping, not adaptive dependencies, full-circle containment, reverse cut-link
body tracking or runtime body editing. 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. Supply all original root edge-frame definitions in their construction order.
Do not pass `current.child_boundary.objects`: those are projected fine-local copies. 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 compares exact ordered circle centers/radii, flags, physical cut links, body
IDs, q, lever arms, hits, BFL support, IDs, q, lever arms, hits, BFL support,
legacy SurfaceActuation specification and current per-link velocity against legacy SurfaceActuation specification and current per-link velocity against
the existing boundary. Omission, changed geometry/actuation and changed body the existing boundary. Omission, changed geometry/actuation or changed physical
rectangle fail without device reads or allocations; reordered bodies fail 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. 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 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 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 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 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. old fluid children. The helper checks old/new physical boundary and velocity
Its immutable `pinned_rectangles` is a canonical, deduplicated tuple, empty for body-free layouts; rectangle input equality too. Its immutable `pinned_rectangles` is legacy containment metadata,
order does not affect it. Scalar-parent window offsets remain part of the not a remap eligibility constraint; it may be empty for a seam-spanning body.
coordinate conversion. 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 `tests/validation/multigrid/test_pinned_body_remap_gpu.py`, covering no-op
next-step bitwise identity, moved-fluid A→B→A, current-amplitude preservation, next-step bitwise identity, moved-fluid A→B→A, current-amplitude preservation,
physical boundary/velocity equality, source independence and absolute counts. physical boundary/velocity equality, source independence and absolute counts.