Skip to content

Phase 3: preserve pamica-specific metadata through the MNE wrapper#189

Merged
neuromechanist merged 4 commits into
feature/issue-139-epic-mne-compatfrom
feature/issue-142-phase3-metadata
Jul 18, 2026
Merged

Phase 3: preserve pamica-specific metadata through the MNE wrapper#189
neuromechanist merged 4 commits into
feature/issue-139-epic-mne-compatfrom
feature/issue-142-phase3-metadata

Conversation

@neuromechanist

Copy link
Copy Markdown
Member

Summary

An mne.preprocessing.ICA carries no source-density family, GG shape, or component-sharing state, so those pamica-specific fitted parameters would be silently dropped by the to_mne_ica export. This phase exposes them as read accessors on AMICAICA (and, for parity, on AMICA/AMICATorchNG).

Closes #142. Part of epic #139. Read-only metadata accessors — no algorithm or fit-path change.

What's new

  • get_pdftype(model_idx=...) -> (n_components,) source-density family code (0-4) per component. PDFTYPE_NAMES (exported from pamica.mne_compat and pamica.torch_impl) maps codes to names: generalized_gaussian, super_gaussian_cosh, gaussian, logistic, sub_gaussian_cosh. Codes differ per component only under the adaptive switcher (pdftype=1, Adaptive-PDF selection for AMICATorchNG #26).
  • get_rho(model_idx=...) -> (n_mix, n_components) generalized-Gaussian shape parameters (meaningful for pdftype=0).
  • shared_components() -> one group of (model_idx, component_idx) pairs per column merged across models by share_comps (Implement component sharing (share_comps) in AMICATorchNG #60); empty when nothing is shared (a single model, or a default multi-model fit with sharing off).

All three exist on AMICATorchNG (guarded on unfitted), delegate through AMICA (with the degenerate-fit _check_usable guard), and are surfaced on AMICAICA with keyword-only model_idx validated against the fitted backend's model count.

Test plan

  • Backend pamica/tests/torch_tests/test_ng_metadata.py (base CI, real EEG): default family is generalized Gaussian; a fixed pdftype=2 fit reports Gaussian; rho shape/bounds; shared_components empty for 1-model and default 2-model, and non-empty with a valid (>=2 models) structure when share_comps merges (real fit, comp_thresh=0.9); unfit guards; PDFTYPE_NAMES covers 0-4.
  • MNE wrapper (test-mne job): per-model get_pdftype/get_rho, PDFTYPE_NAMES mapping, empty shared_components by default, model_idx bounds, and the unfitted guard.
  • Local gates green: ruff, ty check ., mkdocs build --strict, typos, and the backend (7) + mne (39) suites on real sample EEG.

@neuromechanist

Copy link
Copy Markdown
Member Author

Review (4 Sonnet reviewers: code, tests, silent-failure, comments)

All actionable findings addressed in commit 4a... The code-reviewer found nothing above its confidence bar (verified the comp_list indexing, shared_components grouping against the merge algorithm, and PDFTYPE_NAMES values). No false positives.

Correctness / robustness

  • CRITICAL (silent-failure): negative/out-of-range model_idx gave a silent wrong-model result at the AMICATorchNG/AMICA layer (get_pdftype(model_idx=-1) returned the last model's data via torch negative indexing; out-of-range gave an opaque IndexError). Added a backend AMICATorchNG._check_model_idx (rejects negatives and non-ints with a clear ValueError) and applied it to the new accessors AND, closing the pre-existing gap once, to transform/get_mixing_matrix/get_unmixing_matrix/mir/variance_order (pmi inherits it via transform). The AMICA wrapper inherits the guard by delegation.
  • HIGH (silent-failure): get_rho could return a silent NaN on a degenerate multi-model fit whose aggregate LL didn't trip nan_ll (the wrapper's _check_usable only inspects stop_reason). Added an isfinite guard matching state_dict()'s defense-in-depth.

Docs / comments

  • get_rho docstring said rho is inert for "families 2-4" but it's inert for all non-zero codes 1-4 (self.dorho = pdftype == 0 gates the only rho update; the comment reviewer confirmed empirically). Corrected.
  • Added a caveat to shared_components that a merge synchronizes the comp_list-routed mixture params (mu/alpha/beta/rho) but not the per-source pdtype family code, so under pdftype=1 a shared pair can report different get_pdftype codes.

Tests (the reviewers' gaps)

  • The AMICA-delegation layer had no base-CI coverage: added get_pdftype/get_rho/shared_components to the existing test_amica_ng_wrapper.py unfitted + degenerate-fit guard enumerations, plus a direct AMICA happy-path delegation test.
  • The shared_components test hit total collapse (all 32 merged), so its exclude-filter branch was never exercised. Switched to partial-merge params (share_start=8, share_iter=10, do_newton=True, deterministic 10 groups / comp_used=54/64) and assert 0 < len(groups) < n_sources so non-shared columns are provably excluded.
  • Added: model_idx bounds rejection (positive and negative) at the backend and AMICA layers; per-model get_rho(model_idx=1) differs from model 0; and the adaptive pdftype=1 path surfacing mixed {1,4} codes through get_pdftype.

Local gates green: ruff, ty check ., mkdocs build --strict, typos, and the backend metadata (11) + mne (39) + sharing/posterior (26) suites on real sample EEG.

@neuromechanist
neuromechanist merged commit 81e1f45 into feature/issue-139-epic-mne-compat Jul 18, 2026
7 checks passed
@neuromechanist
neuromechanist deleted the feature/issue-142-phase3-metadata branch July 18, 2026 16:18
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