Skip to content

Commit ee22d23

Browse files
authored
fix(pr250-review): address m2march's code review feedback (#253)
fix(pr250-review): address m2march's code review feedback
2 parents bbe62d7 + d93f582 commit ee22d23

8 files changed

Lines changed: 388 additions & 21 deletions

File tree

CONTRIBUTORS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Contributors
2+
3+
HyPyP is built by a community of researchers and engineers. Thank you to everyone
4+
who has contributed code, ideas, feedback, and bug reports.
5+
6+
## Original Authors
7+
8+
- Florence BRUN
9+
- Anaël AYROLLES
10+
- Phoebe CHEN
11+
- Amir DJALOVSKI
12+
- Yann BEAUXIS
13+
- Suzanne DIKKER
14+
- Guillaume DUMAS
15+
16+
## Contributors
17+
18+
- Ryssa MOFFAT
19+
- Marine Gautier MARTINS
20+
- Rémy RAMADOUR
21+
- Patrice FORTIN
22+
- Ghazaleh RANJBARAN
23+
- Quentin MOREAU
24+
- Caitriona DOUGLAS
25+
- Franck PORTEOUS
26+
- Jonas MAGO
27+
- Juan C. AVENDANO
28+
- Julie BONNAIRE
29+
- Martín A. MIGUEL ([@m2march](https://github.com/m2march)) —
30+
Implemented ACCorr hardware acceleration (numba JIT and PyTorch GPU/MPS backends)
31+
and benchmarking infrastructure as part of BrainHack Montréal 2026 (PR #246, #250).
32+
33+
## How to Contribute
34+
35+
We welcome contributions of all kinds — bug fixes, new features, documentation
36+
improvements, and tutorials. See our
37+
[GitHub Issues](https://github.com/ppsp-team/HyPyP/issues) to get started.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ The **Hy**perscanning **Py**thon **P**ipeline
1414

1515
## Contributors
1616

17-
Original authors: Florence BRUN, Anaël AYROLLES, Phoebe CHEN, Amir DJALOVSKI, Yann BEAUXIS, Suzanne DIKKER, Guillaume DUMAS
18-
New contributors: Ryssa MOFFAT, Marine Gautier MARTINS, Rémy RAMADOUR, Patrice FORTIN, Ghazaleh RANJBARAN, Quentin MOREAU, Caitriona DOUGLAS, Franck PORTEOUS, Jonas MAGO, Juan C. AVENDANO, Julie BONNAIRE, Martín A. MIGUEL
17+
Original authors: Florence BRUN, Anaël AYROLLES, Phoebe CHEN, Amir DJALOVSKI, Yann BEAUXIS, Suzanne DIKKER, Guillaume DUMAS
18+
New contributors: Ryssa MOFFAT, Marine Gautier MARTINS, Rémy RAMADOUR, Patrice FORTIN, Ghazaleh RANJBARAN, Quentin MOREAU, Caitriona DOUGLAS, Franck PORTEOUS, Jonas MAGO, Juan C. AVENDANO, Julie BONNAIRE, Martín A. MIGUEL, [@m2march](https://github.com/m2march) (ACCorr GPU/numba optimizations, BrainHack Montréal 2026)
1919

2020
## Installation
2121

hypyp/analyses.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,50 @@ def compute_sync(complex_signal: np.ndarray, mode: str, epochs_average: bool = T
482482
------
483483
ValueError
484484
If an unsupported connectivity metric is specified
485+
486+
Modes
487+
-----
488+
'plv' : Phase Locking Value
489+
``PLV = |⟨e^{i·Δφ}⟩|`` — ranges [0, 1].
490+
Lachaux et al. (1999). *Human Brain Mapping*, 8(4), 194–208.
491+
'ccorr' : Circular Correlation
492+
Circular analogue of Pearson r using global circular mean centering.
493+
Fisher, N. I. (1995). *Statistical analysis of circular data*. Cambridge University Press.
494+
'accorr' : Adjusted Circular Correlation
495+
Same as ccorr but with per-pair phase centering for improved accuracy.
496+
Supports ``optimization`` parameter (numba / torch).
497+
Zimmermann et al. (2024). *Imaging Neuroscience*, 2.
498+
'coh' : Coherence
499+
``Coh = |⟨S₁₂⟩|² / (⟨|S₁|²⟩·⟨|S₂|²⟩)`` — ranges [0, 1].
500+
Nunez, P. L., & Srinivasan, R. (2006). *Electric fields of the brain*. Oxford University Press.
501+
'imaginary_coh' / 'imcoh' : Imaginary Coherence
502+
``Im(⟨S₁₂⟩) / √(⟨|S₁|²⟩·⟨|S₂|²⟩)`` — volume-conduction resistant.
503+
Nolte et al. (2004). *Clinical Neurophysiology*, 115(10), 2292–2307.
504+
'pli' : Phase Lag Index
505+
``|⟨sign(Im(S₁₂))⟩|`` — ranges [0, 1], insensitive to zero-lag coupling.
506+
Stam et al. (2007). *Human Brain Mapping*, 28(11), 1178–1193.
507+
'wpli' : Weighted Phase Lag Index
508+
``|⟨|Im(S₁₂)|·sign(Im(S₁₂))⟩| / ⟨|Im(S₁₂)|⟩`` — noise-robust PLI.
509+
Vinck et al. (2011). *NeuroImage*, 55(4), 1548–1565.
510+
'envelope_corr' / 'envcorr' : Envelope Correlation
511+
Pearson r between signal envelopes (analytic amplitude).
512+
Hipp et al. (2012). *Nature Neuroscience*, 15(6), 884–890.
513+
'pow_corr' / 'powcorr' : Power Correlation
514+
Pearson r between instantaneous signal power.
515+
516+
See Also
517+
--------
518+
hypyp.sync.PLV : Phase Locking Value class
519+
hypyp.sync.CCorr : Circular Correlation class
520+
hypyp.sync.ACCorr : Adjusted Circular Correlation class (with GPU support)
521+
hypyp.sync.Coh : Coherence class
522+
hypyp.sync.ImCoh : Imaginary Coherence class
523+
hypyp.sync.PLI : Phase Lag Index class
524+
hypyp.sync.WPLI : Weighted Phase Lag Index class
525+
hypyp.sync.EnvCorr : Envelope Correlation class
526+
hypyp.sync.PowCorr : Power Correlation class
527+
528+
For full mathematical details and references, see ``hypyp/sync/README.md``.
485529
"""
486530

487531
n_epoch, n_ch, n_freq, n_samp = complex_signal.shape[1], complex_signal.shape[2], \

hypyp/sync/README.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# `hypyp.sync` — Connectivity Metrics Reference
2+
3+
This module provides modular, backend-optimizable connectivity metrics for
4+
inter-brain synchrony analysis. All metrics are accessible via `compute_sync()`
5+
in `hypyp.analyses` or by direct class instantiation.
6+
7+
---
8+
9+
## Metrics
10+
11+
### Phase Locking Value (`plv`)
12+
13+
**Formula:**
14+
```
15+
PLV = |⟨e^{i·Δφ(t)}⟩_t|
16+
```
17+
where `Δφ(t) = φ₁(t) − φ₂(t)` is the instantaneous phase difference.
18+
19+
Ranges from 0 (no synchrony) to 1 (perfect phase locking).
20+
21+
**Reference:** Lachaux, J.-P., Rodriguez, E., Martinerie, J., & Varela, F. J. (1999).
22+
Measuring phase synchrony in brain signals. *Human Brain Mapping*, 8(4), 194–208.
23+
https://doi.org/10.1002/(SICI)1097-0193(1999)8:4<194::AID-HBM4>3.0.CO;2-C
24+
25+
---
26+
27+
### Circular Correlation (`ccorr`)
28+
29+
**Formula:**
30+
```
31+
CCorr = Σ sin(α₁ − ᾱ₁) · sin(α₂ − ᾱ₂)
32+
─────────────────────────────────────────────────
33+
√[ Σ sin²(α₁ − ᾱ₁) · Σ sin²(α₂ − ᾱ₂) ]
34+
```
35+
where `` is the circular mean of `α`.
36+
37+
**Reference:** Fisher, N. I. (1995). *Statistical analysis of circular data*.
38+
Cambridge University Press.
39+
40+
---
41+
42+
### Adjusted Circular Correlation (`accorr`)
43+
44+
**Formula:** Same structure as CCorr, but the centering values `m_adj` and `n_adj`
45+
are optimized *per channel pair* rather than using the global circular mean:
46+
47+
```
48+
n_adj = −(mean_diff − mean_sum) / 2
49+
m_adj = mean_diff + n_adj
50+
```
51+
where `mean_diff = ∠⟨e^{i(α₁−α₂)}⟩` and `mean_sum = ∠⟨e^{i(α₁+α₂)}⟩`.
52+
53+
This per-pair adjustment corrects for biases introduced by non-uniform phase
54+
distributions, providing a more accurate inter-brain synchrony estimate.
55+
56+
**Reference:** Zimmermann, M., Schultz-Nielsen, K., Dumas, G., & Konvalinka, I. (2024).
57+
Arbitrary methodological decisions skew inter-brain synchronization estimates
58+
in hyperscanning-EEG studies. *Imaging Neuroscience*, 2.
59+
https://doi.org/10.1162/imag_a_00350
60+
61+
**Note:** ACCorr supports hardware acceleration via `optimization` parameter.
62+
See [Optimization Backends](#optimization-backends) below.
63+
64+
---
65+
66+
### Coherence (`coh`)
67+
68+
**Formula:**
69+
```
70+
Coh(f) = |⟨S₁₂(f)⟩|²
71+
─────────────────────────────
72+
⟨|S₁(f)|²⟩ · ⟨|S₂(f)|²⟩
73+
```
74+
where `S₁₂(f)` is the cross-spectrum.
75+
76+
Ranges from 0 to 1. Sensitive to volume conduction.
77+
78+
**Reference:** Nunez, P. L., & Srinivasan, R. (2006). *Electric fields of the brain:
79+
the neurophysics of EEG*. Oxford University Press.
80+
81+
---
82+
83+
### Imaginary Coherence (`imaginary_coh` / `imcoh`)
84+
85+
**Formula:**
86+
```
87+
ImCoh(f) = Im(⟨S₁₂(f)⟩)
88+
─────────────────────────────────────
89+
√[ ⟨|S₁(f)|²⟩ · ⟨|S₂(f)|²⟩ ]
90+
```
91+
92+
Volume-conduction resistant: zero-lag (instantaneous) coupling contributes
93+
only to the real part of coherence, so the imaginary part reflects true
94+
time-lagged interactions.
95+
96+
**Reference:** Nolte, G., et al. (2004). See Coherence above.
97+
98+
---
99+
100+
### Phase Lag Index (`pli`)
101+
102+
**Formula:**
103+
```
104+
PLI = |⟨sign(Im(S₁₂(f)))⟩|
105+
```
106+
107+
Ranges from 0 (no coupling or symmetric phase distribution) to 1
108+
(perfectly asymmetric phase distribution). Insensitive to volume conduction.
109+
110+
**Reference:** Stam, C. J., Nolte, G., & Daffertshofer, A. (2007).
111+
Phase lag index: assessment of functional connectivity from multi channel EEG and
112+
MEG with diminished bias from common sources. *Human Brain Mapping*, 28(11), 1178–1193.
113+
https://doi.org/10.1002/hbm.20346
114+
115+
---
116+
117+
### Weighted Phase Lag Index (`wpli`)
118+
119+
**Formula:**
120+
```
121+
WPLI = |⟨|Im(S₁₂)| · sign(Im(S₁₂))⟩|
122+
─────────────────────────────────
123+
⟨|Im(S₁₂)|⟩
124+
```
125+
126+
Weighted version of PLI that reduces the impact of spectral noise while
127+
maintaining insensitivity to zero-lag coupling.
128+
129+
**Reference:** Vinck, M., Oostenveld, R., van Wingerden, M., Battaglia, F., &
130+
Pennartz, C. M. A. (2011). An improved index of phase-synchronization for
131+
electrophysiological data in the presence of volume-conduction, noise and
132+
sample-size bias. *NeuroImage*, 55(4), 1548–1565.
133+
https://doi.org/10.1016/j.neuroimage.2011.01.055
134+
135+
---
136+
137+
### Envelope Correlation (`envelope_corr` / `envcorr`)
138+
139+
**Formula:**
140+
```
141+
EnvCorr = Pearson r( |s₁(t)|, |s₂(t)| )
142+
```
143+
where `|s(t)|` is the analytic amplitude (envelope) of the signal.
144+
145+
Captures low-frequency amplitude co-fluctuations, often reflecting
146+
slow network dynamics.
147+
148+
**Reference:** Hipp, J. F., Hawellek, D. J., Corbetta, M., Siegel, M., &
149+
Engel, A. K. (2012). Large-scale cortical correlation structure of spontaneous
150+
oscillatory activity. *Nature Neuroscience*, 15(6), 884–890.
151+
https://doi.org/10.1038/nn.3101
152+
153+
---
154+
155+
### Power Correlation (`pow_corr` / `powcorr`)
156+
157+
**Formula:**
158+
```
159+
PowCorr = Pearson r( |s₁(t)|², |s₂(t)|² )
160+
```
161+
Similar to envelope correlation but uses instantaneous power rather than
162+
amplitude. More sensitive to high-amplitude bursts.
163+
164+
---
165+
166+
## Optimization Backends
167+
168+
ACCorr supports three computational backends via the `optimization` parameter
169+
in `compute_sync()` or the class constructor:
170+
171+
| Value | Backend | Device | Notes |
172+
|-------|---------|--------|-------|
173+
| `None` (default) | NumPy | CPU | Standard, no extra dependencies |
174+
| `'auto'` | Best available | Auto | torch → numba → numpy |
175+
| `'numba'` | Numba JIT | CPU | ~2× speedup; install: `poetry install --with optim_numba` |
176+
| `'torch'` | PyTorch | GPU/CPU | ~20× speedup on GPU; install: `poetry install --with optim_torch` |
177+
178+
**Device priority for `'torch'` and `'auto'`:** MPS (Apple Silicon) > CUDA (NVIDIA) > CPU.
179+
MPS and CUDA are mutually exclusive; the best available device is selected automatically.
180+
181+
**Precision note:** MPS uses `float32`, which may introduce numerical differences
182+
of up to ~1e-5 compared to CPU/CUDA (`float64`).
183+
184+
All other metrics currently use numpy only (`optimization` parameter is accepted
185+
but ignored for non-ACCorr metrics).
186+
187+
---
188+
189+
## Usage
190+
191+
```python
192+
from hypyp.analyses import compute_sync
193+
194+
# Standard (numpy)
195+
con = compute_sync(complex_signal, 'accorr')
196+
197+
# With GPU acceleration
198+
con = compute_sync(complex_signal, 'accorr', optimization='torch')
199+
200+
# Direct class instantiation
201+
from hypyp.sync import ACCorr
202+
metric = ACCorr(optimization='auto', show_progress=True)
203+
con = metric.compute(complex_signal_internal, n_samp, transpose_axes)
204+
```

hypyp/sync/accorr.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,21 @@
44
"""
55
Adjusted Circular Correlation (ACCorr) connectivity metric.
66
7-
Optimizations (numba, torch) originally developed by @m2march
8-
as part of BrainHack Montreal 2026 (see PR #246).
7+
ACCorr computes the circular correlation between two phase time-series with
8+
per-pair phase centering, providing a more accurate inter-brain synchrony
9+
estimate than standard circular correlation (ccorr).
10+
11+
Reference: Zimmermann et al. (2024). *Imaging Neuroscience*, 2.
12+
https://doi.org/10.1162/imag_a_00350
13+
14+
Credits
15+
-------
16+
The ``precompute`` optimization strategy (vectorized numerator + loop denominator
17+
with pre-computed per-pair adjustments) was contributed by **Martín A. Miguel**
18+
([@m2march](https://github.com/m2march)) during BrainHack Montréal 2026 (PR #246).
19+
20+
The numba JIT and PyTorch GPU/MPS backends were also developed by @m2march and
21+
integrated into the modular sync architecture in PR #250.
922
"""
1023

1124
from typing import Optional

hypyp/sync/base.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,34 @@ def _resolve_optimization(optimization: Optional[str] = None) -> tuple:
165165
Implements fallback logic with warnings when requested
166166
optimization is not available.
167167
168+
Parameters
169+
----------
170+
optimization : str or None
171+
Requested optimization strategy:
172+
173+
- ``None``: standard numpy, no acceleration (default).
174+
- ``'auto'``: best available backend — tries torch first, then
175+
numba, then falls back to numpy. No warning is emitted.
176+
- ``'numba'``: JIT-compiled loops via numba. Falls back to numpy
177+
with a UserWarning if numba is not installed.
178+
- ``'torch'``: PyTorch tensors with auto-detected GPU (see
179+
``_resolve_torch`` for device priority). Falls back to numpy
180+
with a UserWarning if torch is not installed.
181+
168182
Returns
169183
-------
170184
backend : str
171-
One of 'numpy', 'numba', 'torch'.
185+
One of ``'numpy'``, ``'numba'``, ``'torch'``.
172186
device : str
173-
One of 'cpu', 'mps', 'cuda'.
187+
One of ``'cpu'``, ``'mps'``, ``'cuda'``.
188+
189+
Notes
190+
-----
191+
Fallback cascade for ``'auto'``:
192+
torch (best available device) → numba → numpy
193+
194+
Fallback cascade for ``'torch'`` or ``'numba'`` when unavailable:
195+
requested backend → numpy (with UserWarning)
174196
"""
175197
if optimization is None:
176198
return 'numpy', 'cpu'
@@ -209,7 +231,29 @@ def _resolve_optimization(optimization: Optional[str] = None) -> tuple:
209231

210232
@staticmethod
211233
def _resolve_torch() -> tuple:
212-
"""Resolves the best torch device, with warnings."""
234+
"""
235+
Resolves the best available torch device, with warnings if no GPU found.
236+
237+
Device priority: MPS > CUDA > CPU.
238+
239+
MPS (Metal Performance Shaders) is Apple Silicon's GPU backend and is
240+
checked first. CUDA is checked second for NVIDIA GPUs on Linux/Windows.
241+
The two are mutually exclusive — a machine will have one or the other,
242+
never both. If neither is available, torch runs on CPU with a warning.
243+
244+
Returns
245+
-------
246+
backend : str
247+
Always ``'torch'``.
248+
device : str
249+
One of ``'mps'``, ``'cuda'``, or ``'cpu'``.
250+
251+
Notes
252+
-----
253+
MPS uses 32-bit float precision (``torch.float32``), so numerical
254+
results may differ from CPU/CUDA (64-bit) by up to ~1e-5. Tests
255+
should apply a looser tolerance when MPS is the active device.
256+
"""
213257
if MPS_AVAILABLE:
214258
return 'torch', 'mps'
215259
if CUDA_AVAILABLE:

0 commit comments

Comments
 (0)