FixedPatchStepper: level-indexed API, drop 15 fine/child compatibility views
- Hierarchy documents level/pair/shape/frame conventions; edge_frame(l) replaces the stepper's inline origin/spacing loop. - Public per-level read-only state: layouts, boundaries, jets, leaf. - set_jet_amplitude/read_raw_exchange/read_exchange_window: level= required. - remap_child(child_objects=) -> remap_finest(objects=). - _kernel_configs, _upload_boundary, _observed_boundary/_summarize split construction and observation duplication; behavior unchanged. - fine_level_origin -> fine_level_offset (a (y, x) index, not an origin). - docs: migration map in docs/development/src_coherence.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0c64f821a4
commit
b4eacf7526
@@ -4,7 +4,7 @@
|
|||||||
G-to-G explosion reconstruction. The omitted/default `"constant"` selector retains
|
G-to-G explosion reconstruction. The omitted/default `"constant"` selector retains
|
||||||
the same kernel, launch arguments and device allocations. Projected mode adds two
|
the same kernel, launch arguments and device allocations. Projected mode adds two
|
||||||
compact tables per adjacent pair; recursion, collision, BFL, restriction, buffer
|
compact tables per adjacent pair; recursion, collision, BFL, restriction, buffer
|
||||||
ownership and synchronized publication are unchanged. `remap_child` preserves it.
|
ownership and synchronized publication are unchanged. `remap_finest` preserves it.
|
||||||
|
|
||||||
Runtime eligibility is periodic-root, fully blocked D2Q9/SRT/FP32, body/Jet-free,
|
Runtime eligibility is periodic-root, fully blocked D2Q9/SRT/FP32, body/Jet-free,
|
||||||
without LES; the fixed-patch backend has no forcing interface. Shifted and unshifted
|
without LES; the fixed-patch backend has no forcing interface. Shifted and unshifted
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# src coherence stage (from main 914906e)
|
||||||
|
|
||||||
|
Owner direction 2026-10-28: unified style over historical compatibility, inside `src`.
|
||||||
|
No numerical change is intended; kernel text was moved verbatim (PTX/bit parity is
|
||||||
|
pending a GPU run).
|
||||||
|
|
||||||
|
## Conventions (defined once, in `lbm.hierarchy.Hierarchy`)
|
||||||
|
|
||||||
|
- Level `l = 0 .. depth-1` (0 = root). Pair `hierarchy.pairs[l-1]` builds level `l`.
|
||||||
|
- Array shapes are `(ny, nx)`; rectangles, origins and centers are `(x, y)`.
|
||||||
|
- FixedPatchStepper bodies/Jets use the root EDGE frame (root cell `i` = `[i, i+1)`);
|
||||||
|
uniform `Simulation` objects use the integer-NODE frame. Nothing converts between them.
|
||||||
|
- `Hierarchy.edge_frame(l)` -> `((ox, oy), h)`: cell `(i, j)` of level `l`'s allocated
|
||||||
|
array (ghost band and holes included) covers `[o + h*i, o + h*(i+1))`. A scalar
|
||||||
|
FixedPatch2D window carries the half-coarse-cell ghost shift `bounds - HALO/2` in
|
||||||
|
`fine_origin`; a set's level-wide grid has origin 0. Level 1 boundaries are built from
|
||||||
|
the root config (the wall-aware fields live there); deeper ones from the parent frame.
|
||||||
|
- `FixedPatch2D.fine_level_offset` (was `fine_level_origin`) is an array INDEX offset
|
||||||
|
`(y, x)`, not an `(x, y)` origin.
|
||||||
|
|
||||||
|
## FixedPatchStepper migration map
|
||||||
|
|
||||||
|
| removed / changed | replacement |
|
||||||
|
|---|---|
|
||||||
|
| `patch`, `child_patch` | `hierarchy.pairs[0]`, `hierarchy.pairs[1]` |
|
||||||
|
| `base_layout`, `fine_layout`, `child_layout` (stepper properties) | `layouts[0]`, `layouts[1]`, `layouts[2]` (`FixedPatchSet2D.fine_layout(block)` is unchanged) |
|
||||||
|
| `fine_boundary`, `child_boundary` | `boundaries[1]`, `boundaries[2]` (host geometry; `None` = no bodies/Jets) |
|
||||||
|
| `_jets` | `jets[1]` |
|
||||||
|
| `_boundaries[l]` (dict from L1, level keys) | `_level_boundaries[l]` (list from L0, `None` at L0 unless single-level static circles) |
|
||||||
|
| `_fine_tables`, `_actuated`, `_jet_cache` | `_level_boundaries[1].tables` / `.actuated` / `.jet_cache` |
|
||||||
|
| `_fine_leaf`, `_child_leaf` | `leaf[1]`, `leaf[2]` (copies) |
|
||||||
|
| `_middle_kind` | `hierarchy.kinds[1]` |
|
||||||
|
| `_boundary_hosts` (private) | `_boundaries` (private list behind `boundaries`) |
|
||||||
|
| `remap_child(rects, child_objects=...)` | `remap_finest(rects, objects=...)` |
|
||||||
|
| `set_jet_amplitude(i, a)` (implicit level 1) | `set_jet_amplitude(i, a, level=l)` (required) |
|
||||||
|
| `read_raw_exchange(b)` / `read_exchange_window(b)` (implicit level 1) | `read_raw_exchange(b, level=l)` / `read_exchange_window(b, level=l)` (required) |
|
||||||
|
| `_level_name(l)` "base/fine/child" messages | `"level l"` |
|
||||||
|
| `refinement.MIN_PATCH_GAP` | deleted (no gap policy exists) |
|
||||||
|
| `params.cuh HaloPlan`, `collide_trt_no_force` | deleted (zero references) |
|
||||||
|
|
||||||
|
Kept: `step_count`, constructor keywords (`refinements`, `level_blocks`, `level_objects`,
|
||||||
|
`level_jets`, `coarse_boundary`, `explosion`), `Simulation.set_jet_amplitude(i, a)` (one level),
|
||||||
|
`RemapTopology.from_config(..., child_objects=, child_jets=)` (internal remap helper; "child" =
|
||||||
|
the replaced level relative to its parent).
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- Construction: `_check_levels` (inputs), `_kernel_configs` (per-level kernel configs),
|
||||||
|
`Hierarchy.edge_frame` (geometry frames), `_build_level` / `_build_pair` / `_upload_boundary`
|
||||||
|
(device). Observation reads share `_observed_boundary` + `_summarize`.
|
||||||
|
- Blocked kernels: `operators/block_collision.cuh block_collide` is the one collision extension
|
||||||
|
point of `BlockStep` and `BlockChannelStep`. MRT shares one path; SRT keeps its two explicit
|
||||||
|
arithmetic variants (level: unclamped, no-force, shifted direct-momentum feq; channel: uniform
|
||||||
|
OneStep arithmetic with omega clamp), selected by `BLOCK_ROOT_BC`.
|
||||||
|
- `explosion="projected_linear"` stays an optional capability; `"constant"` stays default.
|
||||||
|
|
||||||
|
## Not done / open
|
||||||
|
|
||||||
|
- No single-`Simulation` facade over FixedPatchStepper: the two own different state models
|
||||||
|
(uniform node frame, body sync, forcing, checkpoint vs. immutable blocked hierarchy, edge
|
||||||
|
frame, remap-as-new-instance). A facade would only forward calls; no concrete need found.
|
||||||
|
- Tests, examples and validation callers are migrated separately (pinned runners stay frozen).
|
||||||
@@ -61,7 +61,7 @@ Stationary whole-circle L2 bodies and Jets (`level_objects[2]`, `level_jets[2]`)
|
|||||||
coordinates and a single L2 boundary object. Body IDs are physical object-list
|
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
|
positions, not region IDs or caller `obj_id`. Complete boundary stencils must
|
||||||
avoid the transfer footprint. Change a child Jet with
|
avoid the transfer footprint. Change a child Jet with
|
||||||
`set_jet_amplitude(index, value, level=2)`; L1 bodies/Jets remain unsupported.
|
`set_jet_amplitude(index, value, level=2)` (`level` is required); L1 bodies/Jets remain unsupported.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ it is; it adds no framework. Line references are to candidate 0f8fdbc3.
|
|||||||
| Class | Contents | How it changes | Owner rule |
|
| Class | Contents | How it changes | Owner rule |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1. Module and its globals | Compile-time (visible to the compiler, fixed per module): `config.h` macros incl. `NX`/`NY`, `COLLISION_MODEL`, `BLOCK_ROOT_BC`, `WALL_*`. Runtime per-module globals (not visible to the compiler; written by host code): `__constant__` `d_params`, `d_w`, `d_cx`, `d_cy`, `d_block_channel`, `d_block_wall`; plus bound kernel handles | Globals are written once by the owner right after `load_module`. Uniform grid: `Simulation._recompile` (construction, body-topology sync) loads a new module and re-uploads; `update_runtime_params` rewrites `d_params` (omega, force, n_objects) between runs. Steppers: never rewritten after construction, so they are immutable for the instance's lifetime although they are runtime values. The blocked slot/reservoir root reads its slot from the `d_block_wall` constant (the former dense route used `WALL_*` macros; both were bit-identical in the parity gates). | Every owner loads its own module (`compiler.load_module` = `module_from_file`, no cache). A loaded module is never shared between live owners: globals are per module, so sharing would share mutable `d_params`. |
|
| 1. Module and its globals | Compile-time (visible to the compiler, fixed per module): `config.h` macros incl. `NX`/`NY`, `COLLISION_MODEL`, `BLOCK_ROOT_BC`, `WALL_*`. Runtime per-module globals (not visible to the compiler; written by host code): `__constant__` `d_params`, `d_w`, `d_cx`, `d_cy`, `d_block_channel`, `d_block_wall`; plus bound kernel handles | Globals are written once by the owner right after `load_module`. Uniform grid: `Simulation._recompile` (construction, body-topology sync) loads a new module and re-uploads; `update_runtime_params` rewrites `d_params` (omega, force, n_objects) between runs. Steppers: never rewritten after construction, so they are immutable for the instance's lifetime although they are runtime values. The blocked slot/reservoir root reads its slot from the `d_block_wall` constant (the former dense route used `WALL_*` macros; both were bit-identical in the parity gates). | Every owner loads its own module (`compiler.load_module` = `module_from_file`, no cache). A loaded module is never shared between live owners: globals are per module, so sharing would share mutable `d_params`. |
|
||||||
| 2. Geometry, layout, tables | flags, curved-link tables, block layouts and halo pairs, transfer (explode/coalesce) tables, block kinds | Immutable for the life of an instance. Uniform grid: rebuilt by body sync. `FixedPatchStepper`: a geometry change is a new instance (`remap_child`), committed only after a successful transfer. | No in-place table edits. |
|
| 2. Geometry, layout, tables | flags, curved-link tables, block layouts and halo pairs, transfer (explode/coalesce) tables, block kinds | Immutable for the life of an instance. Uniform grid: rebuilt by body sync. `FixedPatchStepper`: a geometry change is a new instance (`remap_finest`), committed only after a successful transfer. | No in-place table edits. |
|
||||||
| 3. Controls and dependent boundary columns | Jet amplitude -> per-link `surface_velocity`; body actions (uniform grid) | Between steps, without a table or module rebuild (`set_jet_amplitude`). | Input (index, level, amplitude) is validated before any device write; invalid input changes nothing and the instance stays usable. The host copy commits only after the upload returned. Any error after validation leaves the device column undefined and fails the instance: `FixedPatchStepper` status `failed`, `Simulation._failed`; every later device-touching public method (`Simulation`: `stream`, `set_jet_amplitude`, `initialize`, `sync_bodies`, `run`/`step`, `read_surface_exchange`, `read_body(s)`, `get_macroscopic`, `get_ddf`, `update_runtime_params`, `snapshot`, `restore`, save/load checkpoint) raises; cached host readers and host-side staging (e.g. `get_flags`, `add_body`/`remove_body` before a sync) do not touch the device and stay callable. `close()` still releases resources and stays retryable. |
|
| 3. Controls and dependent boundary columns | Jet amplitude -> per-link `surface_velocity`; body actions (uniform grid) | Between steps, without a table or module rebuild (`set_jet_amplitude`). | Input (index, level, amplitude) is validated before any device write; invalid input changes nothing and the instance stays usable. The host copy commits only after the upload returned. Any error after validation leaves the device column undefined and fails the instance: `FixedPatchStepper` status `failed`, `Simulation._failed`; every later device-touching public method (`Simulation`: `stream`, `set_jet_amplitude`, `initialize`, `sync_bodies`, `run`/`step`, `read_surface_exchange`, `read_body(s)`, `get_macroscopic`, `get_ddf`, `update_runtime_params`, `snapshot`, `restore`, save/load checkpoint) raises; cached host readers and host-side staging (e.g. `get_flags`, `add_body`/`remove_body` before a sync) do not touch the device and stay callable. `close()` still releases resources and stays retryable. |
|
||||||
| 4. Per-step state | population double buffers and their parity, raw-exchange ledgers, observations | Device-only during `step(n)` / `run(n)`; buffer swaps are host-handle swaps. | Valid on the host only after the single wait at the end of the call. |
|
| 4. Per-step state | population double buffers and their parity, raw-exchange ledgers, observations | Device-only during `step(n)` / `run(n)`; buffer swaps are host-handle swaps. | Valid on the host only after the single wait at the end of the call. |
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Tests: `tests/unit/test_refinement_mrt.py`, `tests/validation/multigrid/test_ref
|
|||||||
| `slot_reservoir` | on-site velocity bottom slot, reservoir top, velocity 0, 2 <= slot width < nx, patch rows 4..ny-4 | `lbm/refinement_stepper.py` `FixedPatchStepper.__init__`; `lbm/refinement.py` `prepare_fine_boundary` |
|
| `slot_reservoir` | on-site velocity bottom slot, reservoir top, velocity 0, 2 <= slot width < nx, patch rows 4..ny-4 | `lbm/refinement_stepper.py` `FixedPatchStepper.__init__`; `lbm/refinement.py` `prepare_fine_boundary` |
|
||||||
| `channel` | all levels blocked; Zou-He inlet, pressure-NEQ outlet, stationary bounce-back walls; transfers away from the closure | `lbm/channel.py` `validate_channel_config`, `validate_channel_transfers` |
|
| `channel` | all levels blocked; Zou-He inlet, pressure-NEQ outlet, stationary bounce-back walls; transfers away from the closure | `lbm/channel.py` `validate_channel_config`, `validate_channel_transfers` |
|
||||||
| Explosion | `constant` (default); `projected_linear` only periodic, all-blocked SRT, no bodies/Jets, no LES (experimental) | `lbm/refinement_stepper.py` `FixedPatchStepper.__init__` |
|
| Explosion | `constant` (default); `projected_linear` only periodic, all-blocked SRT, no bodies/Jets, no LES (experimental) | `lbm/refinement_stepper.py` `FixedPatchStepper.__init__` |
|
||||||
| L2 runtime remap (`remap_child`) | periodic three-level L2 sets only, no L0/L1 bodies, identical pinned boundary | `lbm/refinement_stepper.py` `FixedPatchStepper.remap_child` |
|
| L2 runtime remap (`remap_finest`) | periodic three-level L2 sets only, no L0/L1 bodies, identical pinned boundary | `lbm/refinement_stepper.py` `FixedPatchStepper.remap_finest` |
|
||||||
|
|
||||||
Tests: `tests/unit/test_refinement_three_level_multipatch.py`, `tests/validation/multigrid/test_refinement_multipatch.py`,
|
Tests: `tests/unit/test_refinement_three_level_multipatch.py`, `tests/validation/multigrid/test_refinement_multipatch.py`,
|
||||||
`test_refinement_three_level.py`, `test_refinement_three_level_multipatch_gpu.py`, `test_refinement_blocked_base.py`,
|
`test_refinement_three_level.py`, `test_refinement_three_level_multipatch_gpu.py`, `test_refinement_blocked_base.py`,
|
||||||
@@ -84,6 +84,6 @@ Tests: `tests/unit/test_jets.py`, `test_jets_review.py`, `test_shifted_jets.py`,
|
|||||||
|
|
||||||
## Not supported in this release
|
## Not supported in this release
|
||||||
|
|
||||||
3D, dynamic/automatic AMR (tagging, time-varying hierarchies beyond `remap_child`),
|
3D, dynamic/automatic AMR (tagging, time-varying hierarchies beyond `remap_finest`),
|
||||||
moving bodies on refined levels, pressure Jets, Jet checkpoints, LES or TRT on refined
|
moving bodies on refined levels, pressure Jets, Jet checkpoints, LES or TRT on refined
|
||||||
levels, partial (non-dividing) tiles.
|
levels, partial (non-dividing) tiles.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ 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 collection; L2 bodies use `child_objects` as described below.
|
an empty collection; L2 bodies use `objects` as described below.
|
||||||
LBMConfig's channel settings do not select this operator's boundary mode.
|
LBMConfig's channel settings do not select this operator's boundary mode.
|
||||||
Geometry metadata is factory-created;
|
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
|
||||||
@@ -77,7 +77,7 @@ PYTHONPATH=src python -m unittest discover -s tests/unit -p 'test_synchronized_r
|
|||||||
|
|
||||||
## Explicit runtime continuation
|
## Explicit runtime continuation
|
||||||
|
|
||||||
`replacement = current.remap_child(rectangles)` returns a new ready
|
`replacement = current.remap_finest(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
|
||||||
periodic three-level runs with no L0/L1 bodies and an existing L2
|
periodic three-level runs with no L0/L1 bodies and an existing L2
|
||||||
`FixedPatchSet2D`; stationary L2 bodies/Jets follow the support contract below.
|
`FixedPatchSet2D`; stationary L2 bodies/Jets follow the support contract below.
|
||||||
@@ -99,7 +99,7 @@ resources; an actual old-device synchronization/read failure marks the old
|
|||||||
stepper failed, consistent with its existing failure semantics.
|
stepper failed, consistent with its existing failure semantics.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
replacement = current.remap_child([(20, 16, 36, 40)])
|
replacement = current.remap_finest([(20, 16, 36, 40)])
|
||||||
# Explicitly choose the returned continuation only after success.
|
# Explicitly choose the returned continuation only after success.
|
||||||
current.close()
|
current.close()
|
||||||
current = replacement
|
current = replacement
|
||||||
@@ -119,7 +119,7 @@ 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_finest(rectangles, objects=original_objects)`
|
||||||
also accepts stationary Circles inside the L2 union. Each body's **physical
|
also accepts stationary Circles inside the L2 union. Each body's **physical
|
||||||
boundary, complete BFL support and current per-link velocity must remain
|
boundary, complete BFL support and current per-link velocity must remain
|
||||||
identical**. Rectangles are not pinned: they may be split, merged or resized,
|
identical**. Rectangles are not pinned: they may be split, merged or resized,
|
||||||
|
|||||||
@@ -21,7 +21,17 @@ from .refinement import (ACTIVE, OUTER_GHOST, FixedPatch2D, FixedPatchSet2D,
|
|||||||
@dataclass(frozen=True, eq=False)
|
@dataclass(frozen=True, eq=False)
|
||||||
class Hierarchy:
|
class Hierarchy:
|
||||||
"""configs[l]: validated LBMConfig of level l; pairs[l]: tables of (l, l+1);
|
"""configs[l]: validated LBMConfig of level l; pairs[l]: tables of (l, l+1);
|
||||||
kinds[l]: composed per-cell kind (child coverage OUTER on every non-finest level)."""
|
kinds[l]: composed per-cell kind (child coverage OUTER on every non-finest level).
|
||||||
|
|
||||||
|
Index conventions (the single place they are defined):
|
||||||
|
* level l = 0 (root) .. depth-1; pair index p = l-1 builds level l from level p;
|
||||||
|
* array shapes are (ny, nx); rectangles, origins and centers are (x, y);
|
||||||
|
* the root EDGE frame puts root cell i at [i, i+1), centers at i+.5 (bodies and
|
||||||
|
Jets of FixedPatchStepper); uniform Simulation objects use the integer-NODE frame;
|
||||||
|
* cell (i, j) of level l's allocated array (ghost band and holes included) covers
|
||||||
|
[o + h*i, o + h*(i+1)) with (o, h) = edge_frame(l); level 1 is built from the root
|
||||||
|
config directly, deeper levels from their parent frame (see edge_frame).
|
||||||
|
"""
|
||||||
configs: tuple
|
configs: tuple
|
||||||
pairs: tuple
|
pairs: tuple
|
||||||
kinds: tuple
|
kinds: tuple
|
||||||
@@ -30,6 +40,18 @@ class Hierarchy:
|
|||||||
def depth(self):
|
def depth(self):
|
||||||
return len(self.kinds)
|
return len(self.kinds)
|
||||||
|
|
||||||
|
def edge_frame(self, level):
|
||||||
|
"""(origin (x, y), spacing) of level ``level``'s array in the root edge frame.
|
||||||
|
|
||||||
|
Each pair's fine_origin is in parent cells: zero for a set (level-wide grid),
|
||||||
|
the shifted window origin for a scalar FixedPatch2D.
|
||||||
|
"""
|
||||||
|
origin, spacing = (0.0, 0.0), 1.0
|
||||||
|
for pair in self.pairs[:level]:
|
||||||
|
origin = tuple(o + spacing * f for o, f in zip(origin, pair.fine_origin))
|
||||||
|
spacing /= 2
|
||||||
|
return origin, spacing
|
||||||
|
|
||||||
|
|
||||||
def is_rectangle(value):
|
def is_rectangle(value):
|
||||||
value = tuple(value)
|
value = tuple(value)
|
||||||
@@ -86,14 +108,14 @@ def _check_chain(coarse, fine, pair):
|
|||||||
def leaf_partition(h):
|
def leaf_partition(h):
|
||||||
"""Base-cell fraction published as ACTIVE over all levels (exact: powers of two).
|
"""Base-cell fraction published as ACTIVE over all levels (exact: powers of two).
|
||||||
|
|
||||||
Window (scalar) levels are offset by their fine_level_origin first.
|
Window (scalar) levels are offset by their fine_level_offset first.
|
||||||
"""
|
"""
|
||||||
base = np.zeros(h.kinds[0].shape)
|
base = np.zeros(h.kinds[0].shape)
|
||||||
oy = ox = 0
|
oy = ox = 0
|
||||||
for level, kind in enumerate(h.kinds):
|
for level, kind in enumerate(h.kinds):
|
||||||
if level:
|
if level:
|
||||||
pair = h.pairs[level - 1]
|
pair = h.pairs[level - 1]
|
||||||
py, px = (pair.fine_level_origin if isinstance(pair, FixedPatch2D) else (0, 0))
|
py, px = (pair.fine_level_offset if isinstance(pair, FixedPatch2D) else (0, 0))
|
||||||
oy, ox = 2 * oy + py, 2 * ox + px
|
oy, ox = 2 * oy + py, 2 * ox + px
|
||||||
y, x = np.nonzero(kind == ACTIVE)
|
y, x = np.nonzero(kind == ACTIVE)
|
||||||
np.add.at(base, ((y + oy) >> level, (x + ox) >> level), 0.25 ** level)
|
np.add.at(base, ((y + oy) >> level, (x + ox) >> level), 0.25 ** level)
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ class FixedPatch2D:
|
|||||||
return self.bounds[0] - HALO / 2, self.bounds[1] - HALO / 2
|
return self.bounds[0] - HALO / 2, self.bounds[1] - HALO / 2
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fine_level_origin(self) -> tuple[int, int]:
|
def fine_level_offset(self) -> tuple[int, int]:
|
||||||
"""(y, x) of this window's first cell on the level-wide 2:1 fine grid."""
|
"""Array index (y, x) of this window's first cell on the level-wide 2:1 fine grid
|
||||||
|
(an index offset, not an (x, y) origin)."""
|
||||||
return 2 * self.bounds[1] - HALO, 2 * self.bounds[0] - HALO
|
return 2 * self.bounds[1] - HALO, 2 * self.bounds[0] - HALO
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# CelerisLab/lbm/refinement_stepper.py
|
# CelerisLab/lbm/refinement_stepper.py
|
||||||
"""Opt-in fixed patch backend with optional stationary fine-interior circles.
|
"""Blocked fixed 2:1 refinement hierarchy (any depth) with optional stationary circles/Jets.
|
||||||
|
|
||||||
The caller owns the CudaContext and must close this backend before that context.
|
The caller owns the CudaContext and must close this backend before that context.
|
||||||
Only synchronized postcollision_g leaf populations are public. No checkpoint,
|
Only synchronized postcollision_g leaf populations are public. No checkpoint,
|
||||||
forcing or runtime parameter mutation is offered. Bounded manual periodic
|
forcing or runtime parameter mutation besides Jet amplitudes is offered. A bounded
|
||||||
L2 set replacement is available at completed base intervals via remap_child.
|
manual periodic level-2 set replacement is available at completed base intervals
|
||||||
|
via remap_finest. Level/pair/shape/frame conventions: lbm.hierarchy.Hierarchy.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, replace
|
from dataclasses import dataclass, replace
|
||||||
from numbers import Integral
|
from numbers import Integral
|
||||||
@@ -62,8 +63,22 @@ class _Level:
|
|||||||
boundary: object = None
|
boundary: object = None
|
||||||
|
|
||||||
|
|
||||||
def _level_name(level):
|
def _kernel_configs(cfg, pairs, wall_aware):
|
||||||
return ("base", "fine", "child")[level] if level < 3 else f"level {level}"
|
"""Per-level kernel LBMConfig (window shape, viscosity doubling per level).
|
||||||
|
|
||||||
|
Geometry configs live in Hierarchy.configs; level 1 of a slot_reservoir root
|
||||||
|
replaces the root wall fields by the internal bounce-back/velocity ghost setup.
|
||||||
|
"""
|
||||||
|
configs = [replace(cfg)]
|
||||||
|
for index, pair in enumerate(pairs):
|
||||||
|
parent = configs[-1]
|
||||||
|
level = replace(parent, nx=pair.fine_shape[1], ny=pair.fine_shape[0],
|
||||||
|
viscosity=2 * parent.viscosity)
|
||||||
|
if index == 0 and wall_aware:
|
||||||
|
level = replace(level, y_wall_bc="bounce_back", top_wall_bc="velocity",
|
||||||
|
wall_slot_width=0, wall_normal_velocity=0.0, velocity=0.0)
|
||||||
|
configs.append(level)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
|
||||||
MAX_GRID_DIM = 65535 # CUDA gridDim.y/z limit (rows, blocks)
|
MAX_GRID_DIM = 65535 # CUDA gridDim.y/z limit (rows, blocks)
|
||||||
@@ -163,11 +178,18 @@ class FixedPatchStepper:
|
|||||||
lattice velocity); every whole circle and its BFL stencil must fit strictly
|
lattice velocity); every whole circle and its BFL stencil must fit strictly
|
||||||
away from that level's transfer footprint.
|
away from that level's transfer footprint.
|
||||||
|
|
||||||
``explosion="projected_linear"`` selects experimental transverse linear
|
``explosion`` selects the parent->child reconstruction of every pair:
|
||||||
reconstruction on each adjacent pair. Default "constant" is unchanged.
|
``"constant"`` (default) or ``"projected_linear"``, an optional transverse
|
||||||
Projected mode requires periodic root, all-blocked SRT levels without
|
linear reconstruction (Schornbaum-Ruede/Chen style) that is GPU-confirmed
|
||||||
bodies/Jets or LES; this backend supplies no forcing. Geometry is generic,
|
more accurate than constant only on periodic two-level single-rectangle
|
||||||
but union/multilevel GPU and matched-physics qualification remain pending.
|
Taylor-Green cases; union/multilevel GPU and matched-physics qualification
|
||||||
|
remain pending. It requires a periodic root and SRT levels without
|
||||||
|
bodies/Jets or LES; this backend supplies no forcing.
|
||||||
|
|
||||||
|
Per-level read-only state: ``hierarchy.pairs`` (pair l-1 builds level l),
|
||||||
|
``layouts``, ``boundaries`` (host geometry), ``jets`` and ``leaf`` masks,
|
||||||
|
each indexed by level. ``set_jet_amplitude``, ``read_raw_exchange`` and
|
||||||
|
``read_exchange_window`` take an explicit ``level=``.
|
||||||
|
|
||||||
One recursive hop advances every level: a child explodes from its parent's
|
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
|
current hop source, takes two hops, then the parent gathers halos, stages
|
||||||
@@ -233,42 +255,26 @@ class FixedPatchStepper:
|
|||||||
layout = BlockLayout2D.build(*pairs[level - 1].fine_shape, *block,
|
layout = BlockLayout2D.build(*pairs[level - 1].fine_shape, *block,
|
||||||
periodic=(False, False))
|
periodic=(False, False))
|
||||||
if layout.nblocks > MAX_GRID_DIM or layout.local_shape[0] > MAX_GRID_DIM:
|
if layout.nblocks > MAX_GRID_DIM or layout.local_shape[0] > MAX_GRID_DIM:
|
||||||
raise ValueError(f"{_level_name(level)} blocks exceed launch limits")
|
raise ValueError(f"level {level} blocks exceed launch limits")
|
||||||
self._layouts.append(layout)
|
self._layouts.append(layout)
|
||||||
if self._channel:
|
if self._channel:
|
||||||
base = blocked_base_tables(pairs[0], self._layouts[0], np.argsort(pairs[0].coalesce_coarse))
|
base = blocked_base_tables(pairs[0], self._layouts[0], np.argsort(pairs[0].coalesce_coarse))
|
||||||
validate_channel_transfers(pairs[0], self._layouts[0], base[1])
|
validate_channel_transfers(pairs[0], self._layouts[0], base[1])
|
||||||
# Kernel configurations per level (geometry configs live in self.hierarchy).
|
self._level_cfgs = _kernel_configs(cfg, pairs, self._wall_aware)
|
||||||
self._level_cfgs = [replace(cfg)]
|
# Host boundary geometry per level in the root edge frame (Hierarchy.edge_frame).
|
||||||
if pairs:
|
self._boundaries = [None]
|
||||||
fine_cfg = replace(cfg, nx=pairs[0].fine_shape[1], ny=pairs[0].fine_shape[0],
|
|
||||||
viscosity=2 * cfg.viscosity)
|
|
||||||
if self._wall_aware:
|
|
||||||
fine_cfg = replace(fine_cfg, y_wall_bc="bounce_back", top_wall_bc="velocity",
|
|
||||||
wall_slot_width=0, wall_normal_velocity=0.0, velocity=0.0)
|
|
||||||
self._level_cfgs.append(fine_cfg)
|
|
||||||
for pair in pairs[1:]:
|
|
||||||
parent = self._level_cfgs[-1]
|
|
||||||
self._level_cfgs.append(replace(parent, nx=pair.fine_shape[1], ny=pair.fine_shape[0],
|
|
||||||
viscosity=2 * parent.viscosity))
|
|
||||||
# Per-level boundary geometry in the root edge frame (level l spacing 2**-l).
|
|
||||||
self._boundary_hosts = [None]
|
|
||||||
origin, spacing = (0.0, 0.0), 1.0
|
|
||||||
spacings = [spacing]
|
|
||||||
for level in range(1, depth):
|
for level in range(1, depth):
|
||||||
pair = pairs[level - 1]
|
pair = pairs[level - 1]
|
||||||
if level == 1:
|
if level == 1: # the root config carries the wall-aware boundary fields
|
||||||
host = prepare_fine_boundary(cfg, pair, level_objects[1],
|
host = prepare_fine_boundary(cfg, pair, level_objects[1],
|
||||||
wall_aware=self._wall_aware, jets=level_jets[1],
|
wall_aware=self._wall_aware, jets=level_jets[1],
|
||||||
_shifted_blocked=self._ddf_shifting)
|
_shifted_blocked=self._ddf_shifting)
|
||||||
else:
|
else:
|
||||||
|
origin, spacing = self.hierarchy.edge_frame(level - 1)
|
||||||
host = prepare_fine_boundary(self._level_cfgs[level - 1], pair, level_objects[level],
|
host = prepare_fine_boundary(self._level_cfgs[level - 1], pair, level_objects[level],
|
||||||
jets=level_jets[level], parent_origin=origin,
|
jets=level_jets[level], parent_origin=origin,
|
||||||
parent_spacing=spacing, _shifted_blocked=self._ddf_shifting)
|
parent_spacing=spacing, _shifted_blocked=self._ddf_shifting)
|
||||||
self._boundary_hosts.append(host)
|
self._boundaries.append(host)
|
||||||
origin = tuple(o + spacing * f for o, f in zip(origin, pair.fine_origin))
|
|
||||||
spacing /= 2
|
|
||||||
spacings.append(spacing)
|
|
||||||
if not isinstance(observe_raw_exchange, bool):
|
if not isinstance(observe_raw_exchange, bool):
|
||||||
raise ValueError("observe_raw_exchange must be bool")
|
raise ValueError("observe_raw_exchange must be bool")
|
||||||
self._observe_raw_exchange = observe_raw_exchange
|
self._observe_raw_exchange = observe_raw_exchange
|
||||||
@@ -280,7 +286,7 @@ class FixedPatchStepper:
|
|||||||
self._window_valid = False
|
self._window_valid = False
|
||||||
self._window_start = None
|
self._window_start = None
|
||||||
self._exchange_valid = False
|
self._exchange_valid = False
|
||||||
self._exchange_spacing = tuple(spacings)
|
self._exchange_spacing = tuple(self.hierarchy.edge_frame(l)[1] for l in range(depth))
|
||||||
self._exchange_centers = tuple(tuple(tuple(o.geometry.center) for o in objects)
|
self._exchange_centers = tuple(tuple(tuple(o.geometry.center) for o in objects)
|
||||||
for objects in level_objects)
|
for objects in level_objects)
|
||||||
self._level_jets = level_jets
|
self._level_jets = level_jets
|
||||||
@@ -289,7 +295,7 @@ class FixedPatchStepper:
|
|||||||
tables = static_circle_tables(self._layouts[0], level_objects[0])
|
tables = static_circle_tables(self._layouts[0], level_objects[0])
|
||||||
root = _BlockedBoundary(None, tables, (), False)
|
root = _BlockedBoundary(None, tables, (), False)
|
||||||
self._level_boundaries = [root] + [
|
self._level_boundaries = [root] + [
|
||||||
_prepare_blocked_boundary(self._boundary_hosts[level], self._layouts[level],
|
_prepare_blocked_boundary(self._boundaries[level], self._layouts[level],
|
||||||
self._level_cfgs[level], level_jets[level],
|
self._level_cfgs[level], level_jets[level],
|
||||||
_shifted_blocked=self._ddf_shifting)
|
_shifted_blocked=self._ddf_shifting)
|
||||||
for level in range(1, depth)]
|
for level in range(1, depth)]
|
||||||
@@ -297,13 +303,13 @@ class FixedPatchStepper:
|
|||||||
if root is not None:
|
if root is not None:
|
||||||
self._leaf[0] &= ~root.tables["solid"]
|
self._leaf[0] &= ~root.tables["solid"]
|
||||||
for level in range(1, depth):
|
for level in range(1, depth):
|
||||||
host = self._boundary_hosts[level]
|
host = self._boundaries[level]
|
||||||
if host is not None:
|
if host is not None:
|
||||||
self._leaf[level] &= (host.flags.reshape(pairs[level - 1].fine_shape) & SOLID) == 0
|
self._leaf[level] &= (host.flags.reshape(pairs[level - 1].fine_shape) & SOLID) == 0
|
||||||
self._state = [mask.copy() for mask in self._leaf]
|
self._state = [mask.copy() for mask in self._leaf]
|
||||||
if self._channel:
|
if self._channel:
|
||||||
self._leaf[0] &= channel_masks((cfg.ny, cfg.nx))[0]
|
self._leaf[0] &= channel_masks((cfg.ny, cfg.nx))[0]
|
||||||
# Private constructor inputs for the bounded manual L2 continuation.
|
# Private constructor inputs for remap_finest.
|
||||||
self._remap_cfg = replace(cfg)
|
self._remap_cfg = replace(cfg)
|
||||||
self._level_blocks = level_blocks
|
self._level_blocks = level_blocks
|
||||||
self.ctx = context
|
self.ctx = context
|
||||||
@@ -325,21 +331,8 @@ class FixedPatchStepper:
|
|||||||
for index, pair in enumerate(pairs):
|
for index, pair in enumerate(pairs):
|
||||||
self._pairs.append(self._build_pair(index, pair))
|
self._pairs.append(self._build_pair(index, pair))
|
||||||
for level in self._levels:
|
for level in self._levels:
|
||||||
b = level.boundary
|
if level.boundary is not None:
|
||||||
if b is not None:
|
self._upload_boundary(level)
|
||||||
b.flags = self._upload(b.tables["flags"])
|
|
||||||
b.device = {k: self._upload(v) for k, v in b.tables.items()
|
|
||||||
if k in BLOCKED_BOUNDARY_DEVICE_COLUMNS}
|
|
||||||
if self._observe_raw_exchange:
|
|
||||||
b.device.update(rx=self._upload(b.tables["rx"]),
|
|
||||||
ry=self._upload(b.tables["ry"]))
|
|
||||||
count = len(b.tables["cell"])
|
|
||||||
b.last_raw = self._allocate(count * 4 * 4)
|
|
||||||
b.interval_integral = self._allocate(count * 4 * 8)
|
|
||||||
if self._observe_window:
|
|
||||||
b.window_integral = self._allocate(count * 4 * 8)
|
|
||||||
b.accumulate = level.module.get_function("BlockAccumulateExchange")
|
|
||||||
b.kernel = level.module.get_function("BlockCurvedBoundary")
|
|
||||||
except BaseException:
|
except BaseException:
|
||||||
try:
|
try:
|
||||||
self.close()
|
self.close()
|
||||||
@@ -347,6 +340,22 @@ class FixedPatchStepper:
|
|||||||
pass # Preserve the original construction failure.
|
pass # Preserve the original construction failure.
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def _upload_boundary(self, level):
|
||||||
|
"""Device columns, BFL kernel and (when observed) the exchange ledgers of one level."""
|
||||||
|
b = level.boundary
|
||||||
|
b.flags = self._upload(b.tables["flags"])
|
||||||
|
b.device = {k: self._upload(v) for k, v in b.tables.items()
|
||||||
|
if k in BLOCKED_BOUNDARY_DEVICE_COLUMNS}
|
||||||
|
if self._observe_raw_exchange:
|
||||||
|
b.device.update(rx=self._upload(b.tables["rx"]), ry=self._upload(b.tables["ry"]))
|
||||||
|
count = len(b.tables["cell"])
|
||||||
|
b.last_raw = self._allocate(count * 4 * 4)
|
||||||
|
b.interval_integral = self._allocate(count * 4 * 8)
|
||||||
|
if self._observe_window:
|
||||||
|
b.window_integral = self._allocate(count * 4 * 8)
|
||||||
|
b.accumulate = level.module.get_function("BlockAccumulateExchange")
|
||||||
|
b.kernel = level.module.get_function("BlockCurvedBoundary")
|
||||||
|
|
||||||
def _planned_bytes(self):
|
def _planned_bytes(self):
|
||||||
"""Both G buffers, halo pairs and boundary columns of every level (transfer tables excluded).
|
"""Both G buffers, halo pairs and boundary columns of every level (transfer tables excluded).
|
||||||
|
|
||||||
@@ -516,14 +525,15 @@ class FixedPatchStepper:
|
|||||||
self._status = "failed"
|
self._status = "failed"
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def remap_child(self, rectangles, *, child_objects=()):
|
def remap_finest(self, rectangles, *, objects=()):
|
||||||
"""Return a NEW ready stepper with an L2 set replaced at this base time.
|
"""Return a NEW ready stepper with the level-2 set replaced at this base time.
|
||||||
|
|
||||||
Periodic three-level set children only; L0/L1 and block sizes stay fixed.
|
Periodic three-level hierarchies whose level 2 is a set only; levels 0/1 and
|
||||||
A body run requires its original root-edge-frame ``child_objects``:
|
block sizes stay fixed. A body run requires its original root-edge-frame
|
||||||
|
level-2 ``objects``:
|
||||||
stationary circles with identical physical boundary and BFL support.
|
stationary circles with identical physical boundary and BFL support.
|
||||||
Other fluid coverage may change, even in the same connected union.
|
Other fluid coverage may change, even in the same connected union.
|
||||||
Current L2 Jets (including updated amplitudes)
|
Current level-2 Jets (including updated amplitudes)
|
||||||
are preserved. Local projected boundary objects are not valid inputs.
|
are preserved. Local projected boundary objects are not valid inputs.
|
||||||
The lock and stream synchronization certify
|
The lock and stream synchronization certify
|
||||||
current postcollision G and the completed 1:2:4 counts internally.
|
current postcollision G and the completed 1:2:4 counts internally.
|
||||||
@@ -541,19 +551,20 @@ class FixedPatchStepper:
|
|||||||
pairs = self.hierarchy.pairs
|
pairs = self.hierarchy.pairs
|
||||||
if (self._wall_aware or self._channel or self.hierarchy.depth != 3
|
if (self._wall_aware or self._channel or self.hierarchy.depth != 3
|
||||||
or not isinstance(pairs[1], FixedPatchSet2D)
|
or not isinstance(pairs[1], FixedPatchSet2D)
|
||||||
or self._boundary_hosts[1] is not None
|
or self._boundaries[1] is not None
|
||||||
or any(b is not None for b in self._level_boundaries[:2])):
|
or any(b is not None for b in self._level_boundaries[:2])):
|
||||||
raise ValueError("remap_child requires periodic three-level L2 sets with no L0/L1 bodies")
|
raise ValueError("remap_finest requires a periodic three-level hierarchy with a "
|
||||||
|
"level-2 set and no level-0/1 bodies")
|
||||||
cfg = self._remap_cfg
|
cfg = self._remap_cfg
|
||||||
child_objects = tuple(child_objects)
|
objects = tuple(objects)
|
||||||
actual = self._level_boundaries[2]
|
actual = self._level_boundaries[2]
|
||||||
jets = () if actual is None else actual.jets
|
jets = () if actual is None else actual.jets
|
||||||
old = RemapTopology.from_config(cfg, pairs[0], pairs[1].bounds,
|
old = RemapTopology.from_config(cfg, pairs[0], pairs[1].bounds,
|
||||||
child_objects=child_objects, child_jets=jets,
|
child_objects=objects, child_jets=jets,
|
||||||
_shifted_blocked=self._ddf_shifting)
|
_shifted_blocked=self._ddf_shifting)
|
||||||
if (not same_physical_boundary(old.boundary, self._boundary_hosts[2])
|
if (not same_physical_boundary(old.boundary, self._boundaries[2])
|
||||||
or (self._boundary_hosts[2] is not None and actual is None)):
|
or (self._boundaries[2] is not None and actual is None)):
|
||||||
raise ValueError("child_objects must reproduce the original root-edge-frame boundary")
|
raise ValueError("objects must reproduce the original root-edge-frame level-2 boundary")
|
||||||
if actual is not None:
|
if actual is not None:
|
||||||
velocity = actual.tables.get("surface_velocity")
|
velocity = actual.tables.get("surface_velocity")
|
||||||
if velocity is None:
|
if velocity is None:
|
||||||
@@ -561,18 +572,17 @@ class FixedPatchStepper:
|
|||||||
_shifted_blocked=self._ddf_shifting)
|
_shifted_blocked=self._ddf_shifting)
|
||||||
if (not same_physical_boundary(old.boundary, actual.host)
|
if (not same_physical_boundary(old.boundary, actual.host)
|
||||||
or not np.array_equal(old.surface_velocity, velocity)):
|
or not np.array_equal(old.surface_velocity, velocity)):
|
||||||
raise ValueError("child_objects changed boundary ownership or surface velocity")
|
raise ValueError("objects changed boundary ownership or surface velocity")
|
||||||
new = RemapTopology.from_config(cfg, pairs[0], rectangles,
|
new = RemapTopology.from_config(cfg, pairs[0], rectangles,
|
||||||
child_objects=child_objects, child_jets=jets,
|
child_objects=objects, child_jets=jets,
|
||||||
_shifted_blocked=self._ddf_shifting)
|
_shifted_blocked=self._ddf_shifting)
|
||||||
if (not same_physical_boundary(old.boundary, new.boundary)
|
if (not same_physical_boundary(old.boundary, new.boundary)
|
||||||
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
|
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
|
||||||
raise ValueError("remap requires the identical pinned physical boundary and BFL support")
|
raise ValueError("remap requires the identical pinned physical boundary and BFL support")
|
||||||
base_block, fine_block, child_block = self._level_blocks
|
|
||||||
# Check sparse block alignment/launch size before any device read/allocation.
|
# Check sparse block alignment/launch size before any device read/allocation.
|
||||||
layout = new.child.fine_layout(child_block)
|
layout = new.child.fine_layout(self._level_blocks[2])
|
||||||
if layout.nblocks > MAX_GRID_DIM or layout.local_shape[0] > MAX_GRID_DIM:
|
if layout.nblocks > MAX_GRID_DIM or layout.local_shape[0] > MAX_GRID_DIM:
|
||||||
raise ValueError("child blocks exceed launch limits")
|
raise ValueError("level 2 blocks exceed launch limits")
|
||||||
with self.ctx.activate():
|
with self.ctx.activate():
|
||||||
try:
|
try:
|
||||||
self._stream.synchronize()
|
self._stream.synchronize()
|
||||||
@@ -588,8 +598,8 @@ class FixedPatchStepper:
|
|||||||
phase="postcollision_g", **options)
|
phase="postcollision_g", **options)
|
||||||
candidate = type(self)(
|
candidate = type(self)(
|
||||||
cfg, context=self.ctx, refinements=[pairs[0].bounds, new.child.bounds],
|
cfg, context=self.ctx, refinements=[pairs[0].bounds, new.child.bounds],
|
||||||
level_blocks=[base_block, fine_block, child_block],
|
level_blocks=list(self._level_blocks),
|
||||||
level_objects=[(), (), child_objects], level_jets=[(), (), jets],
|
level_objects=[(), (), objects], level_jets=[(), (), jets],
|
||||||
observe_raw_exchange=self._observe_raw_exchange,
|
observe_raw_exchange=self._observe_raw_exchange,
|
||||||
observe_window_exchange=self._observe_window, explosion=self._explosion)
|
observe_window_exchange=self._observe_window, explosion=self._explosion)
|
||||||
try:
|
try:
|
||||||
@@ -606,10 +616,9 @@ class FixedPatchStepper:
|
|||||||
raise
|
raise
|
||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
def set_jet_amplitude(self, index, amplitude, *, level=1):
|
def set_jet_amplitude(self, index, amplitude, *, level):
|
||||||
"""Replace one jet's amplitude between steps; no geometry/table rebuild.
|
"""Replace Jet ``index`` of ``level_jets[level]`` between steps; no geometry/table rebuild.
|
||||||
|
|
||||||
``level=1`` preserves the L1 default; use ``level=2`` for child_jets.
|
|
||||||
Invalid input raises before any change. A device/sync error marks the
|
Invalid input raises before any change. A device/sync error marks the
|
||||||
instance failed (as step/initialize do); host state commits only after
|
instance failed (as step/initialize do); host state commits only after
|
||||||
a successful upload.
|
a successful upload.
|
||||||
@@ -618,7 +627,7 @@ class FixedPatchStepper:
|
|||||||
self._require("new", "ready")
|
self._require("new", "ready")
|
||||||
if (isinstance(level, bool) or not isinstance(level, Integral)
|
if (isinstance(level, bool) or not isinstance(level, Integral)
|
||||||
or not 1 <= level < self.hierarchy.depth):
|
or not 1 <= level < self.hierarchy.depth):
|
||||||
raise ValueError("Jet level must name an existing refined level (1 = first)")
|
raise ValueError("Jet level must name an existing refined level (1 .. depth-1)")
|
||||||
boundary = self._level_boundaries[level]
|
boundary = self._level_boundaries[level]
|
||||||
jets = list(self._level_jets[level])
|
jets = list(self._level_jets[level])
|
||||||
jets[index] = jets[index].with_amplitude(amplitude) # validates first
|
jets[index] = jets[index].with_amplitude(amplitude) # validates first
|
||||||
@@ -758,7 +767,23 @@ class FixedPatchStepper:
|
|||||||
self._window_start = self._step_count
|
self._window_start = self._step_count
|
||||||
self._window_valid = True
|
self._window_valid = True
|
||||||
|
|
||||||
def read_exchange_window(self, body_id, *, level=1):
|
def _observed_boundary(self, level, body_id):
|
||||||
|
if (isinstance(level, bool) or not isinstance(level, Integral)
|
||||||
|
or not 0 <= level < len(self._levels)):
|
||||||
|
raise ValueError("level must name an existing level")
|
||||||
|
b = self._level_boundaries[level]
|
||||||
|
if (b is None or isinstance(body_id, bool) or not isinstance(body_id, Integral)
|
||||||
|
or not 0 <= body_id < len(b.host.objects)):
|
||||||
|
raise ValueError("body_id must name a body on this level")
|
||||||
|
return b
|
||||||
|
|
||||||
|
def _summarize(self, b, rows, level, body_id, window, **kw):
|
||||||
|
return summarize_exchange(rows, b.tables["body_id"], b.tables["aperture"],
|
||||||
|
body_id=body_id, level=level, h=self._exchange_spacing[level],
|
||||||
|
time_end=self._step_count, window=window,
|
||||||
|
reference_center=self._exchange_centers[level][body_id], **kw)
|
||||||
|
|
||||||
|
def read_exchange_window(self, body_id, *, level):
|
||||||
"""Non-destructive read of [window start, current step count] in base-lattice units."""
|
"""Non-destructive read of [window start, current step count] in base-lattice units."""
|
||||||
with self._lock, self.ctx.activate():
|
with self._lock, self.ctx.activate():
|
||||||
self._require("ready")
|
self._require("ready")
|
||||||
@@ -769,26 +794,16 @@ class FixedPatchStepper:
|
|||||||
duration = self._step_count - self._window_start
|
duration = self._step_count - self._window_start
|
||||||
if duration < 1:
|
if duration < 1:
|
||||||
raise RuntimeError("exchange window requires at least one completed base interval")
|
raise RuntimeError("exchange window requires at least one completed base interval")
|
||||||
if (isinstance(level, bool) or not isinstance(level, Integral)
|
b = self._observed_boundary(level, body_id)
|
||||||
or not 0 <= level < len(self._levels)):
|
|
||||||
raise ValueError("level must name an existing level")
|
|
||||||
b = self._level_boundaries[level]
|
|
||||||
if (b is None or isinstance(body_id, bool) or not isinstance(body_id, Integral)
|
|
||||||
or not 0 <= body_id < len(b.host.objects)):
|
|
||||||
raise ValueError("body_id must name a body on this level")
|
|
||||||
rows = np.empty((len(b.tables["cell"]), 4), np.float64)
|
rows = np.empty((len(b.tables["cell"]), 4), np.float64)
|
||||||
try:
|
try:
|
||||||
cuda.memcpy_dtoh(rows, b.window_integral)
|
cuda.memcpy_dtoh(rows, b.window_integral)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
self._status = "failed"
|
self._status = "failed"
|
||||||
raise
|
raise
|
||||||
return summarize_exchange(rows, b.tables["body_id"], b.tables["aperture"],
|
return self._summarize(b, rows, level, body_id, "window", duration=duration)
|
||||||
body_id=body_id, level=level, h=self._exchange_spacing[level],
|
|
||||||
time_end=self._step_count, window="window",
|
|
||||||
reference_center=self._exchange_centers[level][body_id],
|
|
||||||
duration=duration)
|
|
||||||
|
|
||||||
def read_raw_exchange(self, body_id, *, level=1, window="last_base_step"):
|
def read_raw_exchange(self, body_id, *, level, window="last_base_step"):
|
||||||
"""Read raw boundary reaction about its geometric center in the root edge frame.
|
"""Read raw boundary reaction about its geometric center in the root edge frame.
|
||||||
|
|
||||||
Repeated reads and step(0) preserve the window. last_substep samples
|
Repeated reads and step(0) preserve the window. last_substep samples
|
||||||
@@ -802,13 +817,7 @@ class FixedPatchStepper:
|
|||||||
raise RuntimeError("raw exchange observation is disabled")
|
raise RuntimeError("raw exchange observation is disabled")
|
||||||
if not self._exchange_valid:
|
if not self._exchange_valid:
|
||||||
raise RuntimeError("raw exchange requires a completed base interval")
|
raise RuntimeError("raw exchange requires a completed base interval")
|
||||||
if (isinstance(level, bool) or not isinstance(level, Integral)
|
b = self._observed_boundary(level, body_id)
|
||||||
or not 0 <= level < len(self._levels)):
|
|
||||||
raise ValueError("level must name an existing level")
|
|
||||||
b = self._level_boundaries[level]
|
|
||||||
if (b is None or isinstance(body_id, bool) or not isinstance(body_id, Integral)
|
|
||||||
or not 0 <= body_id < len(b.host.objects)):
|
|
||||||
raise ValueError("body_id must name a body on this level")
|
|
||||||
if window not in ("last_base_step", "last_substep"):
|
if window not in ("last_base_step", "last_substep"):
|
||||||
raise ValueError("window must be last_base_step or last_substep")
|
raise ValueError("window must be last_base_step or last_substep")
|
||||||
count = len(b.tables["cell"])
|
count = len(b.tables["cell"])
|
||||||
@@ -818,10 +827,7 @@ class FixedPatchStepper:
|
|||||||
except BaseException:
|
except BaseException:
|
||||||
self._status = "failed"
|
self._status = "failed"
|
||||||
raise
|
raise
|
||||||
return summarize_exchange(rows, b.tables["body_id"], b.tables["aperture"],
|
return self._summarize(b, rows, level, body_id, window)
|
||||||
body_id=body_id, level=level, h=self._exchange_spacing[level],
|
|
||||||
time_end=self._step_count, window=window,
|
|
||||||
reference_center=self._exchange_centers[level][body_id])
|
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
"""Return physical G (FP64 for shifted storage), NaN on non-leaves."""
|
"""Return physical G (FP64 for shifted storage), NaN on non-leaves."""
|
||||||
@@ -889,24 +895,10 @@ class FixedPatchStepper:
|
|||||||
if exc[0] is None:
|
if exc[0] is None:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Read-only compatibility views over the level lists, kept on purpose for the existing
|
# Read-only per-level state, indexed by level (0 = root). Pairs: hierarchy.pairs[l-1].
|
||||||
# tests and docs. Internal code must not use them; it reads hierarchy/_layouts/_level_*.
|
layouts = property(lambda self: tuple(self._layouts))
|
||||||
patch = property(lambda self: self.hierarchy.pairs[0])
|
boundaries = property(lambda self: tuple(self._boundaries),
|
||||||
child_patch = property(lambda self: self.hierarchy.pairs[1] if self.hierarchy.depth > 2 else None)
|
doc="Host boundary geometry per level (None: no bodies/Jets).")
|
||||||
base_layout = property(lambda self: self._layouts[0])
|
jets = property(lambda self: tuple(self._level_jets))
|
||||||
fine_layout = property(lambda self: self._layouts[1])
|
leaf = property(lambda self: tuple(m.copy() for m in self._leaf),
|
||||||
child_layout = property(lambda self: self._layouts[2] if len(self._layouts) > 2 else None)
|
doc="Per-level (ny, nx) masks of public (non-NaN) download cells.")
|
||||||
fine_boundary = property(lambda self: self._boundary_hosts[1])
|
|
||||||
child_boundary = property(lambda self: self._boundary_hosts[2] if len(self._boundary_hosts) > 2
|
|
||||||
else None)
|
|
||||||
_jets = property(lambda self: self._level_jets[1])
|
|
||||||
_boundaries = property(lambda self: {i: b for i, b in enumerate(self._level_boundaries) if i})
|
|
||||||
_fine_tables = property(lambda self: None if self._level_boundaries[1] is None
|
|
||||||
else self._level_boundaries[1].tables)
|
|
||||||
_actuated = property(lambda self: self._level_boundaries[1] is not None
|
|
||||||
and self._level_boundaries[1].actuated)
|
|
||||||
_jet_cache = property(lambda self: None if self._level_boundaries[1] is None
|
|
||||||
else self._level_boundaries[1].jet_cache)
|
|
||||||
_fine_leaf = property(lambda self: self._leaf[1])
|
|
||||||
_child_leaf = property(lambda self: self._leaf[2] if len(self._leaf) > 2 else None)
|
|
||||||
_middle_kind = property(lambda self: self.hierarchy.kinds[1] if self.hierarchy.depth > 2 else None)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user