halo_width migration guide (v0.2 → v0.3.0)#

Changed in version v0.2: Per-axis halo widths via SimulationNode.halo_width(), returning a dict[int, int] ({axis: width}). Pointwise nodes return {}; stencil nodes return a non-empty dict. The legacy boolean requires_halo property was retained as a derived fallback (bool(self.halo_width())) and any subclass overriding requires_halo directly emitted a FutureWarning at class-definition time.

Changed in version v0.3.0: The requires_halo property and the __init_subclass__ compat shim were removed. Subclasses that override requires_halo instead of halo_width now raise MigrationError at class-definition time.

Why this changed#

Pencil decomposition (the Cartesian multi-GPU sharding path, ShardedStencilNode, added in v0.2.1) needs to know each axis’s halo width, not just “do you need a halo at all”: it pads every sharded axis by that axis’s width on each side, [halo | interior | halo], before update_padded runs. The boolean was load-bearing once — when stencils were 1-D and one axis per node — but it silently lost information the moment we shipped 3-D LBM.

ShardedUnstructuredNode (added in v0.3.0) takes no halo width from the node. A node’s neighbours there are the partition layout’s ghost cells, handed to update_padded as [the shard's own cells | ghost cells], so a node written for it declares halo_width() == {}. Since v0.4.0 that wrapper refuses a node that declares a non-empty halo_width(): that is the Cartesian contract, and such a node would read the partition layout as the wrong cells. Shard a stencil node with ShardedStencilNode. (Until v0.4.0 this guide said the unstructured wrapper needed each axis’s halo width too.) The sharding topology guide sets the two layouts side by side.

The v0.2 release kept the boolean as a derived fallback for back-compat and emitted a FutureWarning. v0.3.0 closes the deprecation cycle.

What to do#

If you override halo_width already#

Nothing. You already write to the new API; the v0.3.0 hard-removal doesn’t affect you.

If you override requires_halo only#

Replace the property override with a halo_width() method:

Before (v0.2.x; FutureWarning in v0.2, MigrationError in v0.3.0)#
class MyStencilNode(SimulationNode):
    @property
    def requires_halo(self) -> bool:
        return True

    def update_padded(self, state_padded, boundary_inputs, dt, **kwargs):
        ...
After (v0.3.0+)#
class MyStencilNode(SimulationNode):
    def halo_width(self) -> dict[int, int]:
        # axis → cells per side.  Two-cell halo on axis 0 (a 5-point
        # stencil), one-cell halo on axis 1 (a 3-point stencil).
        return {0: 2, 1: 1}

    def update_padded(self, state_padded, boundary_inputs, dt, **kwargs):
        ...

Pointwise nodes#

Pointwise nodes (no spatial neighbour access) return the empty dict:

class MyPointwiseNode(SimulationNode):
    def halo_width(self) -> dict[int, int]:
        return {}

If your node never overrode halo_width or requires_halo, the base class already returns {} and you don’t need to do anything.

A node written for ShardedUnstructuredNode returns {} too, even though its update_padded reads neighbours: they arrive as the partition layout’s ghost cells, not as a per-axis halo (see above).

Auto-migration tooling#

The MigrationError raised at class- definition time carries structured detail rather than just a string message — err.api_name, err.affected_class, err.replacement, err.migration_guide. Tools can introspect these without grepping the message:

import importlib, traceback
from maddening.warnings import MigrationError

try:
    importlib.import_module("my_pkg.legacy_node")
except MigrationError as err:
    print(f"Migrate {err.affected_class.__qualname__}: "
          f"replace requires_halo with {err.replacement}")

Detecting affected subclasses#

A grep over downstream packages catches most cases. For each match, either replace with halo_width or delete the override entirely if the parent class already returns the right value:

$ grep -rn "def requires_halo\|requires_halo =\|@property" --include="*.py" | grep -A1 "requires_halo"

MIME’s CI runs a grep gate on this.