Skip to content

fix(eeg): resolve NumPy trapezoid compatibility lazily - #36

Merged
aurascoper merged 1 commit into
docs/eeg-methods-scopefrom
fix/numpy-trapezoid-compat
Jul 27, 2026
Merged

fix(eeg): resolve NumPy trapezoid compatibility lazily#36
aurascoper merged 1 commit into
docs/eeg-methods-scopefrom
fix/numpy-trapezoid-compat

Conversation

@aurascoper

Copy link
Copy Markdown
Owner

Scripts/eeg_spectral.py guarded its NumPy version bridge with:

_trapz = getattr(np, "trapezoid", getattr(np, "trapz"))

Python evaluates arguments before calling getattr, so the default runs
unconditionally. NumPy 2.0 removed trapz, so on precisely the version the
fallback exists for, importing the module raises:

AttributeError: module 'numpy' has no attribute 'trapz'

That takes down Scripts/consume-session.py and Tests/eval/test_session_consume.py
with it. The pinned calibration venv (numpy 1.26.4) hid the defect entirely.

Fix

Resolve lazily, and take the namespace as a parameter so both API shapes are
testable from a single interpreter:

def _resolve_trapezoid(namespace):
    trapezoid = getattr(namespace, "trapezoid", None)
    if trapezoid is not None:
        return trapezoid
    return namespace.trapz

Tests

Tests/eval/test_eeg_spectral_compat.py uses fake namespaces for both majors,
plus a TrapzIsFatal namespace whose trapz raises if touched — that one is
the actual regression guard and fails against the old expression.

Verification

environment result
numpy 1.26.4 (pinned venv) 6/6 pass
numpy 2.4.6 6/6 pass
test_session_consume on numpy 2.4.6 crashed at import → 14/14 pass

Scope note

NeuralComposeEEG/src/neuralcompose_eeg/features.py is not affected — it
already guards with hasattr(np, "trapezoid") before reaching trapz, so its
fallback is only evaluated on numpy 1.x where it exists. I incorrectly reported
it as having the same exposure earlier; this PR touches one file plus its test.

Sequencing

Worth landing before the Python CI job: wiring Tests/eval into CI on a modern
NumPy would fail on this bug.

`getattr(np, "trapezoid", getattr(np, "trapz"))` evaluates its default eagerly,
so the fallback lookup runs even when `trapezoid` exists. NumPy 2.0 removed
`trapz`, so on exactly the version the fallback was written for, importing
eeg_spectral raised:

    AttributeError: module 'numpy' has no attribute 'trapz'

That made the module unimportable on numpy>=2, taking consume-session.py and
Tests/eval/test_session_consume.py down with it. The pinned calibration venv
(numpy 1.26) hid it.

Resolve through a helper that only touches `trapz` when `trapezoid` is absent.
The helper takes the namespace as a parameter so both API shapes can be tested
from one interpreter instead of maintaining two NumPy environments.

Verified on numpy 1.26.4 and 2.4.6; test_session_consume goes from crashing at
import to 14/14 on numpy 2.4.6.

NeuralComposeEEG/src/neuralcompose_eeg/features.py is not affected — it already
guards with `hasattr(np, "trapezoid")` before reaching `trapz`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aurascoper
aurascoper merged commit 5488951 into docs/eeg-methods-scope Jul 27, 2026
1 check passed
aurascoper added a commit that referenced this pull request Jul 27, 2026
Brings in #36 (NumPy trapezoid) and #37 (Python contract CI) so this branch is
verified by the non-vacuous job rather than by a local run alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aurascoper added a commit that referenced this pull request Jul 27, 2026
Brings in the structured-state module (#35) that
test_synthetic_structured_state_replay_remains_shadow_only imports, plus the
NumPy fix (#36) and the Python contract CI job (#37).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant