diff --git a/README.md b/README.md index 171beea..556cdc4 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,9 @@ from CelerisLab.lbm.refinement_stepper import FixedPatchStepper cfg = LBMConfig(nx=32, ny=24, viscosity=.1) body = SimObject(0, geometry=CircleGeometry(16.0, 12.0, 2.15), center=(16.0, 12.0), radius=2.15, surface_actuation=SurfaceActuation(0.7, 0.85, 0.002)) # angle, half-width, speed -with CudaContext(0) as ctx, FixedPatchStepper(cfg, (8, 6, 24, 18), context=ctx, - fine_objects=[body], fine_block=(16, 20)) as run: +with CudaContext(0) as ctx, FixedPatchStepper(cfg, context=ctx, refinements=[(8, 6, 24, 18)], + level_blocks=[None, (16, 20)], + level_objects=[(), (body,)]) as run: rest = np.asarray(compute_feq_d2q9(1.0, 0.0, 0.0), np.float32)[:, None, None] # (9, ny, nx) postcollision populations per level; non-leaf cells are discarded. run.initialize(np.broadcast_to(rest, (9, *run.patch.coarse_shape)), @@ -133,19 +134,19 @@ Scope and limits: - Bodies must be stationary circles whose whole boundary stencil lies in the fine active region, clear of the coarse/fine transfer cells; violations are rejected before any GPU allocation. -- `fine_block=(by, bx)` stores the fine level in fixed-size blocks; `by`/`bx` must +- `level_blocks=[None, (by, bx)]` stores the fine level in fixed-size blocks; `by`/`bx` must 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 regions: pass a sequence of `(x0, y0, x1, y1)` rectangles as - `bounds`, with both `fine_block` and `base_block`. Transfers are built once + `bounds`, with both levels blocked (`level_blocks`). 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 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 + with tiles. Order of rectangles does not matter. L1 bodies/Jets (`level_objects[1]`/`level_jets[1]`) 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 @@ -156,8 +157,8 @@ Scope and limits: cfg = LBMConfig(nx=32, ny=16, viscosity=.1) rest = np.asarray(compute_feq_d2q9(1.0, 0.0, 0.0), np.float32)[:, None, None] with CudaContext(0) as ctx, FixedPatchStepper( - cfg, ((4, 4, 12, 12), (16, 4, 24, 12)), context=ctx, - fine_block=(4, 4), base_block=(4, 4)) as run: + cfg, context=ctx, refinements=[((4, 4, 12, 12), (16, 4, 24, 12))], + level_blocks=[(4, 4), (4, 4)]) as run: run.initialize(np.broadcast_to(rest, (9, *run.patch.coarse_shape)), np.broadcast_to(rest, (9, *run.patch.fine_shape))) # (9, 32, 64) run.step(32) @@ -166,12 +167,12 @@ Scope and limits: ``` - 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 + all three `level_blocks` entries. L2 must sit inside L1's ACTIVE cells, away from L1's ghost band and transfer footprint (checked before any GPU allocation). `initialize` and `download` take/return three arrays; each level is NaN on its non-ACTIVE (and child-covered) cells. One coarse step = 2 L1 steps = 4 L2 steps. One L1 patch only. Optional stationary circles and Jets - live on L2 via `child_objects` and `child_jets`; `fine_objects`/`jets` retain + live on L2 via `level_objects[2]` and `level_jets[2]`; L1 bodies/Jets retain their L1 meaning and must be empty in this mode. All object and Jet geometry uses the **L0 edge frame**, not the child bounds' L1 window frame. L2 has dx=dt=0.25, so prescribed lattice velocity is unchanged. Each whole body and @@ -188,9 +189,9 @@ Scope and limits: jet = Jet(Tube((6.5, 8.), 0., 3., .3), Parallel(0.), amplitude=.002) rest = np.asarray(compute_feq_d2q9(1.0, 0.0, 0.0), np.float32)[:, None, None] with CudaContext(0) as ctx, FixedPatchStepper( - cfg, (4, 4, 12, 12), context=ctx, fine_block=(4, 4), base_block=(4, 4), - child=(8, 8, 16, 16), child_block=(4, 4), - child_objects=[body], child_jets=[jet]) as run: + cfg, context=ctx, refinements=[(4, 4, 12, 12), (8, 8, 16, 16)], + level_blocks=[(4, 4)] * 3, level_objects=[(), (), (body,)], + level_jets=[(), (), (jet,)]) as run: shapes = (run.patch.coarse_shape, run.patch.fine_shape, run.child_patch.fine_shape) run.initialize(*(np.broadcast_to(rest, (9, *s)) for s in shapes)) # 16², 24², 24² run.step(4) diff --git a/docs/development/root_channel_candidate.md b/docs/development/root_channel_candidate.md index c8a188f..7639c83 100644 --- a/docs/development/root_channel_candidate.md +++ b/docs/development/root_channel_candidate.md @@ -1,6 +1,6 @@ # Blocked AMR root channel increment -Opt in with `FixedPatchStepper(..., coarse_boundary="channel", base_block=(by,bx), fine_block=(fy,fx))`, or the existing level-list form. Every level must be blocked. The supported boundary is the existing uniform D2Q9/SRT/FP32 double-buffer channel: local Zou-He west inlet, pressure-NEQ east outlet, stationary halfway bounce-back y walls. Uniform and parabolic inlet profiles and shifted/unshifted storage are supported; the existing configuration guards still reject MRT/LES and incompatible storage. This does not change the external DFG MRT reference. +Opt in with `FixedPatchStepper(..., coarse_boundary="channel", refinements=[bounds], level_blocks=[(by,bx), (fy,fx)])`. Every level must be blocked. The supported boundary is the existing uniform D2Q9/SRT/FP32 double-buffer channel: local Zou-He west inlet, pressure-NEQ east outlet, stationary halfway bounce-back y walls. Uniform and parabolic inlet profiles and shifted/unshifted storage are supported; the existing configuration guards still reject MRT/LES and incompatible storage. This does not change the external DFG MRT reference. `step/block_channel.cu` is a small root-only kernel. Fine kernels, transfer kernels and recursive hop scheduling are unchanged. Global coordinates and the global profile height/velocity are separate from block-local storage strides. Corner ownership and collision phases match `OneStep`: diff --git a/docs/fixed_three_level_multipatch.md b/docs/fixed_three_level_multipatch.md index 610890d..2472c9c 100644 --- a/docs/fixed_three_level_multipatch.md +++ b/docs/fixed_three_level_multipatch.md @@ -16,8 +16,8 @@ Keep the existing scalar `child=(x0,y0,x1,y1)` for a single scalar parent patch. Its coordinates retain their original meaning: half-open integer indices in the allocated L1 window, including its ghost band. -The new keyword `child_rectangles_in_parent=[...]` is mutually exclusive with -`child`. It accepts a scalar or a set parent and interprets every rectangle in +A rectangle list as the second `refinements` entry (formerly the keyword +`child_rectangles_in_parent=[...]`) accepts a scalar or a set parent and interprets every rectangle in the actual allocated L1 array, including ghost bands and holes. It never infers region-local coordinates. The physical root cell edge corresponding to an L1 index `(x,y)` is `parent.fine_origin + (x,y)/2`: @@ -47,9 +47,9 @@ tile edges. ```python run = FixedPatchStepper( - cfg, [(4, 4, 20, 28), (24, 4, 44, 28)], context=ctx, - base_block=(4, 4), fine_block=(4, 4), child_block=(4, 4), - child_rectangles_in_parent=[(16, 16, 32, 40), (56, 16, 80, 40)], + cfg, context=ctx, + refinements=[[(4, 4, 20, 28), (24, 4, 44, 28)], [(16, 16, 32, 40), (56, 16, 80, 40)]], + level_blocks=[(4, 4)] * 3, ) ``` @@ -57,7 +57,7 @@ This example uses a 48x32 root. `initialize` and `download` use three Q-major dense arrays. Public arrays and masks still scale with the full allocated extent; sparse device buffers do not remove that host-memory limit. -Stationary whole-circle `child_objects` and `child_jets` use root edge +Stationary whole-circle L2 bodies and Jets (`level_objects[2]`, `level_jets[2]`) use root edge coordinates and a single L2 boundary object. Body IDs are physical object-list positions, not region IDs or caller `obj_id`. Complete boundary stencils must avoid the transfer footprint. Change a child Jet with diff --git a/src/CelerisLab/lbm/refinement_stepper.py b/src/CelerisLab/lbm/refinement_stepper.py index 03c27f6..86310ce 100644 --- a/src/CelerisLab/lbm/refinement_stepper.py +++ b/src/CelerisLab/lbm/refinement_stepper.py @@ -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") @@ -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,53 @@ 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. Each pair requires gap >= 4 + in its own parent-cell units. 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 +162,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 +466,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() diff --git a/tests/unit/test_refinement_three_level_multipatch.py b/tests/unit/test_refinement_three_level_multipatch.py index b11df01..021f5bb 100644 --- a/tests/unit/test_refinement_three_level_multipatch.py +++ b/tests/unit/test_refinement_three_level_multipatch.py @@ -315,21 +315,23 @@ class ConstructorAndSchedule(unittest.TestCase): -class InitialLevels(unittest.TestCase): - """initialize() entry adapter: positional per-level arrays or the legacy names, never both.""" +class RemovedLegacyAPI(unittest.TestCase): + """The pre-level-list constructor/initialize forms are gone and fail loudly.""" - def test_forms(self): - a, b, c = object(), object(), object() - f = host._initial_levels - self.assertEqual(f((a, b), None, None, None), (a, b)) - self.assertEqual(f((a, b, c), None, None, None), (a, b, c)) - self.assertEqual(f((a, b), None, None, c), (a, b, c)) # old initialize(c, f, child_g=g) - self.assertEqual(f((), a, b, None), (a, b)) - self.assertEqual(f((), a, b, c), (a, b, c)) - for args in (((a,), None, None, c), ((a, b, c), None, None, c), ((a,), a, b, None), - ((), a, None, None), ((), None, b, c)): - with self.subTest(args=args), self.assertRaises(TypeError): - f(*args) + def test_removed_constructor_forms_raise_type_error(self): + import inspect + context = Mock() + with self.assertRaises(TypeError): # positional bounds + host.FixedPatchStepper(CFG, PARENTS, context=context) + removed = dict(bounds=PARENTS, fine_block=(4, 4), base_block=(4, 4), fine_objects=(), + jets=(), child=(8, 8, 16, 16), child_block=(4, 4), child_objects=(), + child_jets=(), child_rectangles_in_parent=CHILDREN) + for name, value in removed.items(): + with self.subTest(keyword=name), self.assertRaises(TypeError): + host.FixedPatchStepper(CFG, context=context, **levels(), **{name: value}) + context.activate.assert_not_called() + self.assertEqual(list(inspect.signature(host.FixedPatchStepper.initialize).parameters), + ["self", "level_g"]) # no coarse_g/fine_g/child_g if __name__=='__main__':