v2.0.0
Loading...
Searching...
No Matches
inv_gamma_map.cpp
Go to the documentation of this file.
1//=============================================================================================================
21
22//=============================================================================================================
23// INCLUDES
24//=============================================================================================================
25
26#include "inv_gamma_map.h"
27
28//=============================================================================================================
29// STL INCLUDES
30//=============================================================================================================
31
32#include <cmath>
33#include <algorithm>
34#include <limits>
35
36//=============================================================================================================
37// USED NAMESPACES
38//=============================================================================================================
39
40using namespace INVLIB;
41using namespace Eigen;
42
43//=============================================================================================================
44// DEFINE MEMBER METHODS
45//=============================================================================================================
46
48 const MatrixXd& matGain,
49 const MatrixXd& matData,
50 const MatrixXd& matNoiseCov,
51 int nIterations,
52 double tolerance,
53 double gammaThreshold)
54{
55 const int nChannels = static_cast<int>(matGain.rows());
56 const int nSources = static_cast<int>(matGain.cols());
57 const int nTimes = static_cast<int>(matData.cols());
58
59 // Compute noise covariance inverse once
60 MatrixXd matNoiseCovInv = matNoiseCov.ldlt().solve(MatrixXd::Identity(nChannels, nChannels));
61
62 // Initialize gamma (source variance hyperparameters)
63 VectorXd vecGamma = VectorXd::Ones(nSources);
64 VectorXd vecGammaOld = vecGamma;
65
66 // Active set: all sources initially active
67 std::vector<int> activeIdx(nSources);
68 std::iota(activeIdx.begin(), activeIdx.end(), 0);
69
70 // Full source solution
71 MatrixXd matX = MatrixXd::Zero(nSources, nTimes);
72
73 int actualIterations = 0;
74
75 for (int iter = 0; iter < nIterations; ++iter) {
76 actualIterations = iter + 1;
77
78 const int nActive = static_cast<int>(activeIdx.size());
79 if (nActive == 0)
80 break;
81
82 // Extract active columns of G
83 MatrixXd matG_active(nChannels, nActive);
84 VectorXd vecGamma_active(nActive);
85 for (int i = 0; i < nActive; ++i) {
86 matG_active.col(i) = matGain.col(activeIdx[i]);
87 vecGamma_active(i) = vecGamma(activeIdx[i]);
88 }
89
90 // Gamma as diagonal matrix: Gamma_active = diag(gamma_active)
91 // Data covariance model: C_M = G_active * Gamma_active * G_active^T + NoiseCov
92 MatrixXd matCm = matG_active * vecGamma_active.asDiagonal() * matG_active.transpose() + matNoiseCov;
93
94 const auto ldlt = matCm.ldlt();
95 const MatrixXd matCmInvG = ldlt.solve(matG_active);
96 const MatrixXd matA = matCmInvG.transpose() * matData; // G^T C_M^{-1} M
97
98 // Posterior mean: X_active = Gamma_active * G_active^T * C_M^{-1} * M
99 MatrixXd matX_active = vecGamma_active.asDiagonal() * matA;
100
101 // Write back to full solution
102 matX.setZero();
103 for (int i = 0; i < nActive; ++i) {
104 matX.row(activeIdx[i]) = matX_active.row(i);
105 }
106
107 // MacKay update (Wipf & Nagarajan 2009, eq. 10), as in mne.inverse_sparse.gamma_map
108 vecGammaOld = vecGamma;
109 for (int i = 0; i < nActive; ++i) {
110 const double denom = std::max(matG_active.col(i).dot(matCmInvG.col(i)), std::numeric_limits<double>::epsilon());
111 vecGamma(activeIdx[i]) = vecGamma_active(i) * matA.row(i).squaredNorm() / static_cast<double>(nTimes) / denom;
112 }
113
114 // Prune sources with gamma below threshold
115 std::vector<int> newActive;
116 newActive.reserve(nActive);
117 for (int i = 0; i < nActive; ++i) {
118 int srcIdx = activeIdx[i];
119 if (vecGamma(srcIdx) >= gammaThreshold) {
120 newActive.push_back(srcIdx);
121 } else {
122 vecGamma(srcIdx) = 0.0;
123 }
124 }
125 activeIdx = newActive;
126
127 // Check convergence: max|gamma_new - gamma_old| / max(max|gamma_old|, 1e-10) < tolerance
128 double maxGammaOld = std::max(vecGammaOld.cwiseAbs().maxCoeff(), 1e-10);
129 double maxRelChange = 0.0;
130 for (int idx : activeIdx) {
131 double relChange = std::abs(vecGamma(idx) - vecGammaOld(idx)) / maxGammaOld;
132 maxRelChange = std::max(maxRelChange, relChange);
133 }
134 if (maxRelChange < tolerance)
135 break;
136 }
137
138 // Build result
139 InvGammaMapResult result;
140 result.nIterations = actualIterations;
141 result.vecGamma = vecGamma;
142
143 // Collect active vertices
144 QVector<int> finalActive;
145 for (int i = 0; i < nSources; ++i) {
146 if (vecGamma(i) >= gammaThreshold) {
147 finalActive.append(i);
148 }
149 }
150 result.activeVertices = finalActive;
151
152 // Build source estimate with active rows only
153 const int nActiveFinal = finalActive.size();
154 MatrixXd matActiveSol(nActiveFinal, nTimes);
155 VectorXi vecActiveVerts(nActiveFinal);
156 for (int i = 0; i < nActiveFinal; ++i) {
157 matActiveSol.row(i) = matX.row(finalActive[i]);
158 vecActiveVerts(i) = finalActive[i];
159 }
160
161 result.stc = InvSourceEstimate(matActiveSol, vecActiveVerts, 0.0f, 1.0f);
163
164 // Compute residual norm ||M - G*X||_F
165 MatrixXd matResidual = matData - matGain * matX;
166 result.residualNorm = matResidual.norm();
167
168 return result;
169}
Gamma-MAP sparse Bayesian inverse solver — automatic-relevance-determination prior on per-source vari...
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
InvSourceEstimate stc
QVector< int > activeVertices
static InvGammaMapResult compute(const Eigen::MatrixXd &matGain, const Eigen::MatrixXd &matData, const Eigen::MatrixXd &matNoiseCov, int nIterations=100, double tolerance=1e-6, double gammaThreshold=1e-10)