Merge 580fcf65 (grok/p1-docs-wording) for integration review
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,52 +65,8 @@ def _level_name(level):
|
||||
return ("base", "fine", "child")[level] if level < 3 else f"level {level}"
|
||||
|
||||
|
||||
def _legacy_levels(bounds, *, fine_block, base_block, child, child_block, child_rectangles_in_parent,
|
||||
fine_objects, jets, child_objects, child_jets):
|
||||
"""Normalise the original two/three-level keywords to per-level lists (original errors)."""
|
||||
if child is not None and child_rectangles_in_parent is not None:
|
||||
raise ValueError("child and child_rectangles_in_parent are mutually exclusive")
|
||||
nested = child is not None or child_rectangles_in_parent is not None
|
||||
bounds = tuple(bounds)
|
||||
if not is_rectangle(bounds) and (fine_block is None or base_block is None):
|
||||
raise ValueError("several patches require fine_block and base_block")
|
||||
fine_objects, jets = tuple(fine_objects), tuple(jets)
|
||||
child_objects, child_jets = tuple(child_objects), tuple(child_jets)
|
||||
if not nested and (child_objects or child_jets):
|
||||
raise ValueError("child_objects/child_jets require a nested child")
|
||||
if base_block is not None and fine_block is None:
|
||||
raise ValueError("base_block requires fine_block")
|
||||
refinements, blocks = [bounds], [base_block, fine_block]
|
||||
objects, level_jets = [(), fine_objects], [(), jets]
|
||||
if nested:
|
||||
if child is not None and not is_rectangle(bounds):
|
||||
raise ValueError("scalar child requires one parent patch; use child_rectangles_in_parent")
|
||||
if fine_block is None or base_block is None or child_block is None:
|
||||
raise ValueError("a nested child needs fine_block, base_block and child_block")
|
||||
if fine_objects or jets:
|
||||
raise ValueError("a nested child supports bodies/Jets only on L2; use child_objects/child_jets")
|
||||
refinements.append(tuple(child) if child is not None else tuple(child_rectangles_in_parent))
|
||||
blocks.append(child_block)
|
||||
objects.append(child_objects)
|
||||
level_jets.append(child_jets)
|
||||
return refinements, blocks, objects, level_jets
|
||||
|
||||
|
||||
def _initial_levels(level_g, coarse_g, fine_g, child_g):
|
||||
"""One tuple of per-level arrays from initialize()'s positional or legacy named form."""
|
||||
if coarse_g is None and fine_g is None:
|
||||
if child_g is not None and len(level_g) != 2:
|
||||
raise TypeError("child_g needs exactly the coarse and fine arrays before it")
|
||||
return tuple(level_g) + (() if child_g is None else (child_g,))
|
||||
if level_g:
|
||||
raise TypeError("pass level arrays positionally or as coarse_g/fine_g/child_g, not both")
|
||||
if coarse_g is None or fine_g is None:
|
||||
raise TypeError("coarse_g and fine_g are both required in the named form")
|
||||
return (coarse_g, fine_g) + (() if child_g is None else (child_g,))
|
||||
|
||||
|
||||
def _check_levels(refinements, blocks, objects, jets):
|
||||
"""Candidate limits shared by the list and the legacy entry points."""
|
||||
"""Candidate limits of the level lists, checked before any device work."""
|
||||
depth = len(refinements) + 1
|
||||
if depth < 2:
|
||||
raise ValueError("at least one refinement level is required")
|
||||
@@ -119,13 +75,13 @@ def _check_levels(refinements, blocks, objects, jets):
|
||||
raise ValueError(f"{name} needs one entry per level ({depth})")
|
||||
for level, block in enumerate(blocks):
|
||||
if block is not None and len(block) != 2:
|
||||
raise ValueError(f"{_level_name(level)}_block must be (by, bx)")
|
||||
raise ValueError(f"level_blocks[{level}] must be (by, bx)")
|
||||
if objects[0] or jets[0]:
|
||||
raise ValueError("base-level bodies/Jets are not supported")
|
||||
if any(blocks[level - 1] is not None and blocks[level] is None for level in range(1, depth)):
|
||||
raise ValueError("a blocked level requires blocked refined levels below it")
|
||||
if not is_rectangle(refinements[0]) and (blocks[0] is None or blocks[1] is None):
|
||||
raise ValueError("several patches require fine_block and base_block")
|
||||
raise ValueError("several L1 patches require level_blocks[0] and level_blocks[1]")
|
||||
if depth > 2:
|
||||
if any(block is None for block in blocks):
|
||||
raise ValueError("three or more levels must all be blocked")
|
||||
@@ -134,7 +90,14 @@ def _check_levels(refinements, blocks, objects, jets):
|
||||
|
||||
|
||||
class FixedPatchStepper:
|
||||
"""D2Q9/SRT/FP32, one unforced internal 2:1 patch in an x-periodic box.
|
||||
"""D2Q9 SRT/MRT FP32 fixed 2:1 refinement hierarchy, built from level lists.
|
||||
|
||||
``refinements[l]`` gives level l+1 in level-l integer cells: one rectangle
|
||||
(x0, y0, x1, y1) (FixedPatch2D, a fine window with its own origin) or a
|
||||
sequence of rectangles (FixedPatchSet2D, a dependency-validated union on one
|
||||
level-wide grid, public fine arrays (2*ny, 2*nx), NaN outside ACTIVE cells);
|
||||
see lbm.hierarchy. ``level_blocks``, ``level_objects`` and ``level_jets``
|
||||
hold one entry per level, L0 included (L0 bodies/Jets are not supported).
|
||||
|
||||
``coarse_boundary`` is ``periodic`` (default) or ``slot_reservoir``:
|
||||
a bottom on-site slot and top fixed-density reservoir from ``cfg``.
|
||||
@@ -145,68 +108,55 @@ class FixedPatchStepper:
|
||||
actual channel closure footprint. Channel remapping is not supported.
|
||||
Periodic mode does not apply the wall/slot boundary fields in ``cfg``.
|
||||
Outer walls remain coarse; fine halo donors must be ordinary fluid.
|
||||
``fine_objects`` accepts fixed circles in coarse physical coordinates;
|
||||
velocities are in coarse lattice units (dx=dt=1). Independent local copies
|
||||
are projected at fine cell centers. Their complete stencil must be active
|
||||
and disjoint from the actual two-hop transfer footprint.
|
||||
These use the edge/cell-center frame (coarse centers I+.5), whereas ordinary Simulation objects use the integer-node frame.
|
||||
|
||||
``initialize(coarse_g, fine_g)`` accepts physical Q-major (9,ny,nx) arrays
|
||||
already in postcollision phase. It does not collide or build channel flags.
|
||||
Every ``step()`` publishes only after both levels complete on one stream.
|
||||
On a device/launch failure the instance becomes unusable: close and recreate
|
||||
it, rather than pretending the partially advanced fine buffer rolled back.
|
||||
Bodies are fixed circles in root (L0) physical coordinates; velocities are
|
||||
in root lattice units (dx=dt=1). Independent local copies are projected at
|
||||
fine cell centers; their complete stencil must be active and disjoint from
|
||||
the actual two-hop transfer footprint. These use the edge/cell-center frame
|
||||
(coarse centers I+.5), whereas ordinary Simulation objects use the
|
||||
integer-node frame. Jets (body.jets.Jet) use the same frame and drive cut
|
||||
links selected at their true hits; ``set_jet_amplitude(index, amplitude,
|
||||
level=l)`` re-uploads the per-link velocity column only.
|
||||
|
||||
``initialize(*level_g)`` accepts one physical Q-major (9, ny, nx) array per
|
||||
level, already in postcollision phase. It does not collide or build channel
|
||||
flags. Every ``step()`` publishes only after all levels complete on one
|
||||
stream. On a device/launch failure the instance becomes unusable: close and
|
||||
recreate it, rather than pretending a partially advanced buffer rolled back.
|
||||
|
||||
A ``level_blocks`` entry (by, bx) stores that level in blocks.cu storage:
|
||||
the ENTIRE level shape, including the four-cell patch ghost band, is
|
||||
partitioned by a non-periodic BlockLayout2D with one-cell same-level halos.
|
||||
Patch kinds stay per cell (OUTER invalid, INNER stream only, ACTIVE
|
||||
collide); halos are gathered before each hop. Public arrays keep the dense
|
||||
shapes; only the allocation differs. A blocked level requires blocked
|
||||
refined levels below it; several L1 rectangles and depth >= 3 require all
|
||||
levels blocked. Blocked bodies are stationary circles, optionally
|
||||
surface-actuated: prepare_fine_boundary decides legality on the dense grid,
|
||||
then cut links are staged receiver-local per hop: halo -> BFL -> step, same
|
||||
buffer. A blocked parent gathers halos before receiver-local coalescence
|
||||
staging and pull/collision; covered cells are poisoned on every step.
|
||||
|
||||
``fine_block=(by, bx)`` (opt-in) stores the fine level in
|
||||
blocks.cu storage: the ENTIRE fine_shape, including the four-cell patch
|
||||
ghost band, is partitioned by a non-periodic BlockLayout2D with one-cell
|
||||
same-level halos. Patch kinds stay per cell (OUTER invalid, INNER stream
|
||||
only, ACTIVE collide); halos are gathered before each fine hop. Public
|
||||
arrays keep the dense (9, ny, nx) fine shape; only the allocation differs.
|
||||
Blocked fine_objects are stationary circles, optionally surface-actuated:
|
||||
prepare_fine_boundary decides legality on the dense fine grid, then cut
|
||||
links are staged receiver-local per hop: halo -> BFL -> step, same buffer.
|
||||
``base_block=(by, bx)`` also blocks the parent; it requires ``fine_block``.
|
||||
Parent halos precede receiver-local coalescence staging and pull/collision;
|
||||
covered cells are explicitly poisoned on every step. Public shapes stay dense.
|
||||
``observe_raw_exchange=True`` records blocked physical cut-link exchange
|
||||
without changing populations. read_raw_exchange reports the last complete
|
||||
base interval or last substep in base-lattice units per unit depth. This
|
||||
is boundary momentum reaction, not a validated nozzle/stress load. Dense
|
||||
body layouts are not supported by this new diagnostic in this candidate.
|
||||
body levels are not supported by this diagnostic.
|
||||
``observe_window_exchange=True`` (requires observe_raw_exchange) adds a second
|
||||
per-link FP64 ledger accumulated by the same kernel between
|
||||
start_exchange_window() and read_exchange_window(); disabled by default.
|
||||
``jets`` (body.jets.Jet) use the same edge frame as ``fine_objects`` and
|
||||
drive fine cut links selected at their true hits; ``set_jet_amplitude``
|
||||
re-uploads the per-link velocity column only.
|
||||
|
||||
Several patches: pass ``bounds`` as a sequence of (x0, y0, x1, y1)
|
||||
rectangles (FixedPatchSet2D, dependency-validated union). This requires
|
||||
fine_block and base_block; fine_objects/jets are projected once onto the
|
||||
level-wide fine grid (each circle inside the union, body ids = object
|
||||
positions, jet and writer checks level-wide), and
|
||||
uses one level-wide fine grid (2*ny, 2*nx): public fine arrays have that
|
||||
shape, NaN outside the patches' ACTIVE cells. The step sequence is the
|
||||
blocked one above, with unique union transfers in one launch each.
|
||||
|
||||
Three-level separated unions: ``child_rectangles_in_parent=[...]`` is
|
||||
mutually exclusive with scalar ``child`` and accepts either parent form.
|
||||
Coordinates index the allocated L1 array, including ghost bands and holes:
|
||||
root edge = patch.fine_origin + L1 index / 2. A parent set has origin zero;
|
||||
a scalar parent retains its shifted window origin. The child set's public
|
||||
extent is twice the allocated L1 shape, even though storage is sparse.
|
||||
Each pair requires gap >= 4 in its own parent-cell units. All regions share
|
||||
one buffer pair per level and the fixed 1:2:4 schedule; L2-only bodies/Jets
|
||||
below apply unchanged, with object IDs assigned over the whole level.
|
||||
|
||||
Three-level single chain: ``child`` bounds use L1 window cells; all three
|
||||
levels must be blocked. ``child_objects`` and ``child_jets`` use the L0
|
||||
edge frame, with L2 dx=dt=.25 and unchanged lattice velocity. L1 bodies
|
||||
and Jets remain unsupported in a chain. Every whole circle and its BFL
|
||||
stencil must fit strictly away from the L2 transfer footprint. L2 runs
|
||||
halo -> BFL -> step four times per base step. Runtime child Jet changes
|
||||
require ``set_jet_amplitude(index, amplitude, level=2)``.
|
||||
Nested levels: a child set's coordinates index the allocated parent array,
|
||||
including ghost bands and holes: root edge = parent fine_origin + index / 2
|
||||
(zero origin for a parent set; a scalar parent keeps its shifted window
|
||||
origin). A scalar child requires a scalar parent. Rectangle unions impose no
|
||||
fixed gap rule: adjacent, overlapping and concave unions are accepted when
|
||||
their actual transfer dependencies close (FixedPatchSet2D /
|
||||
nest_patch_set). All regions share one buffer pair per level and the fixed
|
||||
schedule of 2**l hops per base step at level l. Bodies/Jets are supported only on the
|
||||
finest level of a nested hierarchy (L2 dx=dt=.25 in root units, unchanged
|
||||
lattice velocity); every whole circle and its BFL stencil must fit strictly
|
||||
away from that level's transfer footprint.
|
||||
|
||||
``explosion="projected_linear"`` selects experimental transverse linear
|
||||
reconstruction on each adjacent pair. Default "constant" is unchanged.
|
||||
@@ -214,43 +164,21 @@ class FixedPatchStepper:
|
||||
bodies/Jets or LES; this backend supplies no forcing. Geometry is generic,
|
||||
but union/multilevel GPU and matched-physics qualification remain pending.
|
||||
|
||||
Level lists (any depth): ``refinements[l]`` gives level l+1 in level-l
|
||||
integer cells (scalar window or rectangle list, see lbm.hierarchy);
|
||||
``level_blocks``/``level_objects``/``level_jets`` hold one entry per level,
|
||||
L0 included. They are mutually exclusive with the legacy keywords above,
|
||||
which are normalised to the same lists. One recursive hop advances every
|
||||
level: a child explodes from its parent's current hop source, takes two
|
||||
hops, then the parent gathers halos, stages restriction, applies BFL and
|
||||
steps. Depth >= 3 must be fully blocked with bodies/Jets on the finest level.
|
||||
One recursive hop advances every level: a child explodes from its parent's
|
||||
current hop source, takes two hops, then the parent gathers halos, stages
|
||||
restriction, applies BFL and steps.
|
||||
"""
|
||||
def __init__(self, cfg, bounds=None, *, context, coarse_boundary="periodic", fine_objects=(),
|
||||
fine_block=None, base_block=None, jets=(), child=None, child_block=None,
|
||||
child_objects=(), child_jets=(), child_rectangles_in_parent=None,
|
||||
refinements=None, level_blocks=None, level_objects=None, level_jets=None,
|
||||
def __init__(self, cfg, *, context, refinements, coarse_boundary="periodic",
|
||||
level_blocks=None, level_objects=None, level_jets=None,
|
||||
observe_raw_exchange=False, observe_window_exchange=False, explosion="constant"):
|
||||
if HALO != 4:
|
||||
raise RuntimeError("refinement.cu requires a four-cell fine halo")
|
||||
if coarse_boundary not in ("periodic", "slot_reservoir", "channel"):
|
||||
raise ValueError("coarse_boundary must be periodic, slot_reservoir or channel")
|
||||
if refinements is None:
|
||||
if bounds is None:
|
||||
raise ValueError("bounds or refinements is required")
|
||||
if level_blocks is not None or level_objects is not None or level_jets is not None:
|
||||
raise ValueError("level_blocks/level_objects/level_jets require refinements")
|
||||
refinements, level_blocks, level_objects, level_jets = _legacy_levels(
|
||||
bounds, fine_block=fine_block, base_block=base_block, child=child,
|
||||
child_block=child_block, child_rectangles_in_parent=child_rectangles_in_parent,
|
||||
fine_objects=fine_objects, jets=jets, child_objects=child_objects,
|
||||
child_jets=child_jets)
|
||||
else:
|
||||
if (bounds is not None or child is not None or child_rectangles_in_parent is not None
|
||||
or fine_block is not None or base_block is not None or child_block is not None
|
||||
or tuple(fine_objects) or tuple(jets) or tuple(child_objects) or tuple(child_jets)):
|
||||
raise ValueError("refinements and the legacy patch keywords are mutually exclusive")
|
||||
depth = len(refinements) + 1
|
||||
level_blocks = [None] * depth if level_blocks is None else list(level_blocks)
|
||||
level_objects = [()] * depth if level_objects is None else list(level_objects)
|
||||
level_jets = [()] * depth if level_jets is None else list(level_jets)
|
||||
depth = len(refinements) + 1
|
||||
level_blocks = [None] * depth if level_blocks is None else list(level_blocks)
|
||||
level_objects = [()] * depth if level_objects is None else list(level_objects)
|
||||
level_jets = [()] * depth if level_jets is None else list(level_jets)
|
||||
level_objects = [tuple(o) for o in level_objects]
|
||||
level_jets = [tuple(j) for j in level_jets]
|
||||
level_blocks = [None if b is None else tuple(b) for b in level_blocks]
|
||||
@@ -540,13 +468,13 @@ class FixedPatchStepper:
|
||||
if self._ddf_shifting and any(layout is None for layout in self._layouts):
|
||||
raise ValueError("shifted populations require all-blocked levels")
|
||||
|
||||
def initialize(self, *level_g, coarse_g=None, fine_g=None, child_g=None):
|
||||
def initialize(self, *level_g):
|
||||
"""Upload physical postcollision G, encoding before any FP32 rounding.
|
||||
|
||||
Shifted input should be physical FP64 to retain small perturbations.
|
||||
Public initialization resets the step count; non-leaves are discarded.
|
||||
"""
|
||||
given = _initial_levels(level_g, coarse_g, fine_g, child_g)
|
||||
given = tuple(level_g)
|
||||
with self._lock, self.ctx.activate():
|
||||
self._require("new", "ready")
|
||||
self._check_storage()
|
||||
@@ -670,10 +598,9 @@ class FixedPatchStepper:
|
||||
old, new, source, base_count=count, level_steps=(count, 2*count, 4*count),
|
||||
phase="postcollision_g", **options)
|
||||
candidate = type(self)(
|
||||
cfg, pairs[0].bounds, context=self.ctx, base_block=base_block,
|
||||
fine_block=fine_block, child_block=child_block,
|
||||
child_rectangles_in_parent=new.child.bounds,
|
||||
child_objects=child_objects, child_jets=jets,
|
||||
cfg, context=self.ctx, refinements=[pairs[0].bounds, new.child.bounds],
|
||||
level_blocks=[base_block, fine_block, child_block],
|
||||
level_objects=[(), (), child_objects], level_jets=[(), (), jets],
|
||||
observe_raw_exchange=self._observe_raw_exchange,
|
||||
observe_window_exchange=self._observe_window, explosion=self._explosion)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user