From addf4beb1529c6b89a39d2caf3a14412430681e6 Mon Sep 17 00:00:00 2001 From: PragnyaKhandelwal Date: Tue, 3 Mar 2026 16:16:45 +0530 Subject: [PATCH 1/5] ENH: compute CSD directly for upper-triangle channel pairs --- mne/time_frequency/csd.py | 46 +++++++++++++++------------------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/mne/time_frequency/csd.py b/mne/time_frequency/csd.py index 4ddaa0ac6a3..e4b67fdaed3 100644 --- a/mne/time_frequency/csd.py +++ b/mne/time_frequency/csd.py @@ -1406,22 +1406,14 @@ def _csd_fourier(X, sfreq, n_times, freq_mask, n_fft): x_mt, _ = _mt_spectra(X, np.hanning(n_times), sfreq, n_fft) # Hack so we can sum over axis=-2 - weights = np.array([1.0])[:, np.newaxis, np.newaxis, np.newaxis] + weights = np.array([1.0])[np.newaxis, :, np.newaxis] x_mt = x_mt[:, :, freq_mask] - # Calculating CSD - # Tiling x_mt so that we can easily use _csd_from_mt() - x_mt = x_mt[:, np.newaxis, :, :] - x_mt = np.tile(x_mt, [1, x_mt.shape[0], 1, 1]) - y_mt = np.transpose(x_mt, axes=[1, 0, 2, 3]) - weights_y = np.transpose(weights, axes=[1, 0, 2, 3]) - csds = _csd_from_mt(x_mt, y_mt, weights, weights_y) - - # FIXME: don't compute full matrix in the first place - csds = np.array( - [_sym_mat_to_vector(csds[:, :, i]) for i in range(csds.shape[-1])] - ).T + # Calculate CSD for upper-triangle channel pairs directly. + # This avoids computing/storing the full channel x channel matrix. + ii, jj = np.triu_indices(x_mt.shape[0]) + csds = _csd_from_mt(x_mt[ii], x_mt[jj], weights, weights) # Scaling by number of samples and compensating for loss of power # due to windowing (see section 11.5.2 in Bendat & Piersol). @@ -1445,27 +1437,23 @@ def _csd_multitaper( _, weights = _psd_from_mt_adaptive( x_mt, eigvals, freq_mask, max_iter, return_weights=True ) - # Tiling weights so that we can easily use _csd_from_mt() - weights = weights[:, np.newaxis, :, :] - weights = np.tile(weights, [1, x_mt.shape[0], 1, 1]) else: # Do not use adaptive weights - weights = np.sqrt(eigvals)[np.newaxis, np.newaxis, :, np.newaxis] + weights = np.sqrt(eigvals)[np.newaxis, :, np.newaxis] x_mt = x_mt[:, :, freq_mask] - # Calculating CSD - # Tiling x_mt so that we can easily use _csd_from_mt() - x_mt = x_mt[:, np.newaxis, :, :] - x_mt = np.tile(x_mt, [1, x_mt.shape[0], 1, 1]) - y_mt = np.transpose(x_mt, axes=[1, 0, 2, 3]) - weights_y = np.transpose(weights, axes=[1, 0, 2, 3]) - csds = _csd_from_mt(x_mt, y_mt, weights, weights_y) - - # FIXME: don't compute full matrix in the first place - csds = np.array( - [_sym_mat_to_vector(csds[:, :, i]) for i in range(csds.shape[-1])] - ).T + # Calculate CSD for upper-triangle channel pairs directly. + # This avoids computing/storing the full channel x channel matrix. + ii, jj = np.triu_indices(x_mt.shape[0]) + x_mt_i = x_mt[ii] + x_mt_j = x_mt[jj] + if adaptive: + weights_i = weights[ii] + weights_j = weights[jj] + else: + weights_i = weights_j = weights + csds = _csd_from_mt(x_mt_i, x_mt_j, weights_i, weights_j) # Scaling by sampling frequency for compatibility with Matlab csds /= sfreq From 819e7eb346757b857db4e7fcbb8b9baec0c4f114 Mon Sep 17 00:00:00 2001 From: PragnyaKhandelwal Date: Thu, 5 Mar 2026 12:52:44 +0530 Subject: [PATCH 2/5] DOC: added towncrier entry and suggested changes --- doc/changes/dev/13719.newfeature.rst | 1 + doc/changes/names.inc | 1 + 2 files changed, 2 insertions(+) create mode 100644 doc/changes/dev/13719.newfeature.rst diff --git a/doc/changes/dev/13719.newfeature.rst b/doc/changes/dev/13719.newfeature.rst new file mode 100644 index 00000000000..33dc74512ff --- /dev/null +++ b/doc/changes/dev/13719.newfeature.rst @@ -0,0 +1 @@ +Improve memory usage and runtime of :func:`mne.time_frequency.csd_array_fourier` and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by :newcontrib:`Pragnya Khandelwal`. \ No newline at end of file diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 09c5818b6af..5f108e95e4c 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -258,6 +258,7 @@ .. _Pierre Guetschel: https://github.com/PierreGtch .. _Pierre-Antoine Bannier: https://github.com/PABannier .. _Ping-Keng Jao: https://github.com/nafraw +.. _Pragnya Khandelwal: https://github.com/PragnyaKhandelwal .. _Proloy Das: https://github.com/proloyd .. _Qian Chu: https://github.com/qian-chu .. _Qianliang Li: https://www.dtu.dk/english/service/phonebook/person?id=126774 From 7b2e6b56c04dc0f44cd223fdcf173a36abdc1e48 Mon Sep 17 00:00:00 2001 From: PragnyaKhandelwal Date: Thu, 5 Mar 2026 18:14:11 +0530 Subject: [PATCH 3/5] DOC: clarify affected CSD APIs in changelog entry --- doc/changes/dev/13719.newfeature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/dev/13719.newfeature.rst b/doc/changes/dev/13719.newfeature.rst index 33dc74512ff..ed6bc97c13a 100644 --- a/doc/changes/dev/13719.newfeature.rst +++ b/doc/changes/dev/13719.newfeature.rst @@ -1 +1 @@ -Improve memory usage and runtime of :func:`mne.time_frequency.csd_array_fourier` and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by :newcontrib:`Pragnya Khandelwal`. \ No newline at end of file +Improve memory usage and runtime of :func:`mne.time_frequency.csd_fourier`, :func:`mne.time_frequency.csd_multitaper`, :func:`mne.time_frequency.csd_array_fourier`, and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by :newcontrib:`Pragnya Khandelwal`. \ No newline at end of file From 13b9371aeed5b36f3f19a4a5df834032408ef52b Mon Sep 17 00:00:00 2001 From: PragnyaKhandelwal Date: Thu, 5 Mar 2026 22:36:27 +0530 Subject: [PATCH 4/5] DOC: update contributor format in changelog entry for CSD functions --- doc/changes/dev/13719.newfeature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/dev/13719.newfeature.rst b/doc/changes/dev/13719.newfeature.rst index ed6bc97c13a..c75a66e17cd 100644 --- a/doc/changes/dev/13719.newfeature.rst +++ b/doc/changes/dev/13719.newfeature.rst @@ -1 +1 @@ -Improve memory usage and runtime of :func:`mne.time_frequency.csd_fourier`, :func:`mne.time_frequency.csd_multitaper`, :func:`mne.time_frequency.csd_array_fourier`, and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by :newcontrib:`Pragnya Khandelwal`. \ No newline at end of file +Improve memory usage and runtime of :func:`mne.time_frequency.csd_fourier`, :func:`mne.time_frequency.csd_multitaper`, :func:`mne.time_frequency.csd_array_fourier`, and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by `Pragnya Khandelwal`. \ No newline at end of file From e96f5b930ac228f1c4b3d12f3ebe4d929f420688 Mon Sep 17 00:00:00 2001 From: PragnyaKhandelwal Date: Fri, 6 Mar 2026 10:05:04 +0530 Subject: [PATCH 5/5] DOC: fix Pragnya contributor link in 13719 towncrier --- doc/changes/dev/13719.newfeature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/dev/13719.newfeature.rst b/doc/changes/dev/13719.newfeature.rst index c75a66e17cd..78a736b6477 100644 --- a/doc/changes/dev/13719.newfeature.rst +++ b/doc/changes/dev/13719.newfeature.rst @@ -1 +1 @@ -Improve memory usage and runtime of :func:`mne.time_frequency.csd_fourier`, :func:`mne.time_frequency.csd_multitaper`, :func:`mne.time_frequency.csd_array_fourier`, and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by `Pragnya Khandelwal`. \ No newline at end of file +Improve memory usage and runtime of :func:`mne.time_frequency.csd_fourier`, :func:`mne.time_frequency.csd_multitaper`, :func:`mne.time_frequency.csd_array_fourier`, and :func:`mne.time_frequency.csd_array_multitaper` by avoiding unnecessary full-matrix CSD construction, by `Pragnya Khandelwal`_. \ No newline at end of file