pamica vs. AMICA: every deliberate difference¶
pamica reproduces Jason Palmer's Fortran AMICA numerically, and parity with that reference is how correctness is defined here. This page lists every place pamica deliberately behaves differently, why, and how to restore the reference behavior.
Anything not on this page is intended to match the reference. If you find a difference that is not listed, that is a bug worth reporting — not a documented choice.
At a glance¶
| # | Area | Fortran AMICA | pamica default | Why | Restore reference |
|---|---|---|---|---|---|
| 1 | Rank threshold | absolute floor mineig=1e-15 |
relative floor mineig_rel=1e-12 |
the absolute floor is unit-dependent: MEG in Tesla yields rank 0, and average-referenced EEG is detected by luck | mineig_rel=None |
| 2 | Zero numerical rank | numeigs = 0, continues |
ValueError naming cause and fix |
fitting a zero-dimensional model is not a recoverable state | — (no reason to want it) |
| 3 | Returned iterate | last EM iterate | highest-likelihood iterate (keep_best) |
the lrate schedule is non-monotone; late Newton overshoots cut LL variance 12.7x → 2.0x | keep_best=False |
| 4 | Newton | on (do_newton=1) |
off | isolates the algorithm from initialization for parity work | do_newton=True |
| 5 | Degenerate fits | returns NaN sources | refuses transform/get_*/save |
NaN sources silently poison downstream analysis | — (see ADR 0003, issue #50) |
| 6 | Precision | float64 | float64 (float32 on Apple GPUs) | Apple GPUs have no float64; float32 agrees to ~7 significant digits, not bit-parity | dtype=torch.float64 |
| 7 | Sensor-space maps | Spinv applied internally |
get_sensor_mixing_matrix() |
get_mixing_matrix() returns sphered-space A; switching its meaning by data conditioning would be worse |
— |
Rows 1, 2 and 7 arrived with ADR 0004; row 3 with ADR 0003; row 5 with issue #50.
1. Relative rank threshold (the one changed default)¶
The reference decides how many dimensions are real with an absolute floor on covariance eigenvalues:
Because the floor is absolute, it depends on the physical units of the input:
- MEG in Tesla has covariance eigenvalues ~1e-26. Every one is below
1e-15, so the reference computesnumeigs = 0. - Average-referenced EEG — an everyday preprocessing step — sits at
lambda_min/lambda_max = 8.5e-17on the bundled sample. Whether the rank-deficient dimension is caught depends on the recording's absolute scale.
pamica defaults to a scale-free floor instead, mineig_rel * largest_eigenvalue. On real
EEG projected to rank 20:
| threshold | rank found | reconstruction error |
|---|---|---|
mineig=1e-15 (reference) |
24 | 1.98e-09 |
mineig_rel=1e-12 (pamica) |
20 | 7.52e-15 |
This does not affect ordinary data. Real EEG has lambda_min/lambda_max ~ 5e-4,
eight orders of magnitude above the relative floor, so nothing is reduced and results are
bit-identical to the reference. The difference appears only where the reference was
already unreliable.
mineig_rel replaces the absolute floor rather than combining with it — for Tesla-scale
data a relative floor is ~1e-35 in absolute terms, so taking the larger of the two would
silently discard it.
from pamica import AMICA
AMICA().fit(X) # relative floor (default)
AMICA().fit(X, mineig_rel=None) # exactly the reference's absolute floor
AMICA().fit(X, mineig_rel=1e-9) # stricter rank detection
Working with rank-deficient data¶
Rank deficiency is routine — Maxwell filtering, average referencing, channel interpolation all cause it. pamica sizes the model to the detected rank, so a 306-channel MEG recording at rank 70 yields 70 sources, not 306.
m = AMICA().fit(X) # X is (306, T), numerical rank ~70
m.model_.n_channels # 70 - sources actually estimated
m.model_.n_channels_in # 306 - input channels
S = m.transform(X) # (70, T) sources
A = m.model_.get_sensor_mixing_matrix() # (306, 70) scalp maps
get_mixing_matrix() returns A in the sphered space. Use
get_sensor_mixing_matrix() for anything sensor-shaped (topographies, dipole fitting,
export) — when rank reduction is active the sphere is non-square and only the
pseudo-inverse maps back.
Mixed channel types (MEG)¶
Magnetometers and gradiometers have different physical units, and the difference is not cosmetic: scaling barely affects a full-rank fit, but it decides which directions survive rank truncation. On mixed data, badly scaled input retained 72.6% of signal variance against 99.2% for correctly scaled input.
The MNE wrapper handles this for you, following MNE's own ICA convention — one std per
channel type, applied as X / pre_whitener_:
from pamica.mne_compat import AMICAICA
fitted = AMICAICA().fit(raw) # channel types scaled automatically
fitted.pre_whitener_ # (n_channels, 1) scale actually applied
For a single channel type this changes nothing: AMICA's sphering absorbs a global rescale exactly (verified — the two spheres' ratio is one constant to ~1e-15).
Using the array API (pamica.AMICA) directly, scale by channel type yourself before
fitting; there is no info from which to infer types.
Backend differences¶
Separate from reference divergences: the optional MLX backend is a subset.
| Feature | PyTorch | NumPy | MLX | Native Fortran |
|---|---|---|---|---|
| Newton | yes | yes | NotImplementedError |
yes |
| PDF families | all five | all five | GG only | all five |
| Component sharing | yes | yes | no | yes |
| Outlier rejection | yes | yes | no | yes |
| Precision | f64/f32 | f64 | f32 only | f64 |
| Rank detection | yes | yes | yes | yes (absolute floor) |
MLX limitations raise NotImplementedError rather than differing silently, per
.rules/backend_parity.md.