Edge validation: migration guide (v0.2 → v0.3.0)#

Added in version v0.2: Shape, dtype, and unit checks at GraphManager.compile(), emitted as warning subclasses of (the then-named) EdgeValidationWarning.

Changed in version v0.2.1: Shape and dtype mismatches were promoted to hard errorscompile() now raises an :class:ExceptionGroup of ShapeMismatchError / DtypeMismatchError. Unit mismatches stay as warnings (units are documentation, not contract).

Changed in version v0.3.0: The deprecated EdgeValidationWarning, ShapeMismatchWarning, and DtypeMismatchWarning aliases were removed per the v0.2.1 deprecation cycle. UnitMismatchWarning now roots at UserWarning directly.

In v0.2 we added compile-time validation that walks every EdgeSpec and compares the source field’s runtime shape, dtype, and units against the target node’s BoundaryInputSpec. The intent is that a lab newcomer wiring a 20-edge graph gets every mistake flagged in one compile() pass, instead of debugging one failure at a time at runtime.

This page is for users with existing graphs that started seeing warnings after upgrading to v0.2 and want to migrate to v0.2.1’s error-on-mismatch behaviour.

The validation surface (current — v0.2.1)#

Issue

Behaviour

Edge references a non-existent node

RuntimeError from compile()

Source field not in source node’s state

RuntimeError

Shape mismatch (no transform)

ShapeMismatchError raised inside an ExceptionGroup

Dtype mismatch (no transform)

DtypeMismatchError raised inside an ExceptionGroup

Unit mismatch

UnitMismatchWarning (advisory; never raises)

Disconnected node

UserWarning (suppressed in single-node graphs)

All shape/dtype errors detected during a single compile() call are aggregated and raised together — the lab-newcomer “see every problem at once” property is preserved by the ExceptionGroup shape.

The escape hatch: any transform= on the edge suppresses the check#

When an edge has a transform= callable, both shape and dtype checks are skipped — we cannot statically reason about what the transform will produce. Use this when the mismatch is intentional (e.g. picking a boundary slice from a 1-D field).

gm.add_edge(
    source_node="heat_rod",
    target_node="ball",
    source_field="temperature",        # shape (N,)
    target_field="ambient_temperature",  # spec shape ()
    transform=lambda T: T[N // 2],      # → scalar at midpoint
)

No ShapeMismatchError is raised because the transform is doing the reshape.

Catching the errors#

ExceptionGroup is a builtin on Python 3.11+; MADDENING declares exceptiongroup as a backport dependency on 3.10. Two equivalent ways to handle the group:

Python 3.11+ — except*#

from maddening.warnings import ShapeMismatchError, DtypeMismatchError

try:
    gm.compile()
except* ShapeMismatchError as eg:
    for err in eg.exceptions:
        print("shape:", err)
except* DtypeMismatchError as eg:
    for err in eg.exceptions:
        print("dtype:", err)

Python 3.10 (or version-agnostic) — explicit iteration#

from maddening.warnings import (
    EdgeValidationError, ExceptionGroup,
    ShapeMismatchError, DtypeMismatchError,
)

try:
    gm.compile()
except ExceptionGroup as eg:
    for err in eg.exceptions:
        if isinstance(err, ShapeMismatchError):
            print("shape:", err)
        elif isinstance(err, DtypeMismatchError):
            print("dtype:", err)
        elif isinstance(err, EdgeValidationError):
            print("other validation error:", err)

Common patterns#

“I’m slicing a boundary from a 1-D field”#

# Before v0.2: silent at compile, surprise at runtime
gm.add_edge("heat", "neighbor", "temperature", "wall_T")  # shape (N,) vs ()

# v0.2+: ShapeMismatchError on compile() — fix by adding the
# transform that was previously implicit
gm.add_edge(
    "heat", "neighbor", "temperature", "wall_T",
    transform=lambda T: T[-1],
)

“I’m passing kilonewtons but the target wants newtons”#

gm.add_edge(
    "thruster", "body", "force", "F_external",
    source_units="kN",
    transform=lambda x: x * 1000.0,   # kN → N
    target_units="N",
)

The transform suppresses the shape/dtype checks; the explicit target_units declaration matches the target spec so no UnitMismatchWarning fires either.

“I have a synthetic test that needs the error”#

import pytest
from maddening.warnings import ShapeMismatchError, ExceptionGroup

with pytest.raises(ExceptionGroup) as exc_info:
    gm.compile()
assert any(isinstance(e, ShapeMismatchError) for e in exc_info.value.exceptions)

“I had pytest.warns(ShapeMismatchWarning) in my test suite”#

Changed in version v0.3.0: The deprecated ShapeMismatchWarning / DtypeMismatchWarning / EdgeValidationWarning aliases were removed in v0.3.0 per the v0.2.1 deprecation cycle. UnitMismatchWarning now roots at :class:UserWarning directly.

Update to the error form above. Importing any of the removed aliases raises ImportError.

“I’m running MIME experiments and don’t want CI to fail”#

MADDENING ships filterwarnings = ["error", ...] in pyproject.toml for the internal test suite — downstream projects don’t inherit that setting. In v0.2, the typical downstream override was:

[tool.pytest.ini_options]
filterwarnings = [
    "error",
    # The two lines below were ``ignore::maddening.warnings.{Shape,Dtype}MismatchWarning``
    # before v0.3.0; remove them entirely -- those classes are gone.
    "ignore::maddening.warnings.UnitMismatchWarning",
]

In v0.3.0+ the only filter you need is the UnitMismatchWarning ignore (units stay advisory by contract). Any *MismatchWarning ignores left over from v0.2.x cause pytest to fail to parse the config, so delete them.

Aggregation: all problems in one pass#

compile() walks the full validate() issue list and accumulates every shape/dtype mismatch into a single ExceptionGroup. Unit mismatches still emit as warnings before the raise, so they show up in caught records too.

import warnings
import pytest
from maddening.warnings import (
    ExceptionGroup,
    ShapeMismatchError, DtypeMismatchError, UnitMismatchWarning,
)

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always", UnitMismatchWarning)
    with pytest.raises(ExceptionGroup) as exc_info:
        gm.compile()

errors = exc_info.value.exceptions
unit_warns = [w for w in caught
              if issubclass(w.category, UnitMismatchWarning)]

A 20-edge graph with three different shape mismatches, two different dtype mismatches, and one unit mismatch produces six items total (five errors in the group + one warning) — by design.

Backwards-compat escape hatch#

The deprecated *Warning classes (ShapeMismatchWarning, DtypeMismatchWarning, EdgeValidationWarning) stay importable through v0.2.x. Nothing in MADDENING emits them in v0.2.1, but they are not removed — downstream pytest.warns references resolve and just never fire. In v0.3 the aliases are removed (see the v0.3.0 plan’s compat-hygiene bucket).