v2.0.0
Loading...
Searching...
No Matches
inv_mxne.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
25#include "inv_mxne.h"
26
27//=============================================================================================================
28// STL INCLUDES
29//=============================================================================================================
30
31#include <cmath>
32#include <algorithm>
33
34//=============================================================================================================
35// USED NAMESPACES
36//=============================================================================================================
37
38using namespace INVLIB;
39using namespace Eigen;
40
41//=============================================================================================================
42// DEFINE MEMBER METHODS
43//=============================================================================================================
44
46 const MatrixXd& matGain,
47 const MatrixXd& matData,
48 double alpha,
49 int nIterations,
50 double tolerance)
51{
52 const int nChannels = static_cast<int>(matGain.rows());
53 const int nSources = static_cast<int>(matGain.cols());
54 const int nTimes = static_cast<int>(matData.cols());
55
56 // Precompute G^T * G and G^T * M
57 MatrixXd matGtG = matGain.transpose() * matGain;
58 MatrixXd matGtM = matGain.transpose() * matData;
59
60 // Initialize weights to 1
61 VectorXd vecWeights = VectorXd::Ones(nSources);
62 VectorXd vecWeightsOld = vecWeights;
63
64 // Active set: all sources initially active
65 std::vector<int> activeIdx(nSources);
66 std::iota(activeIdx.begin(), activeIdx.end(), 0);
67
68 // Full source solution
69 MatrixXd matX = MatrixXd::Zero(nSources, nTimes);
70
71 int actualIterations = 0;
72
73 for (int iter = 0; iter < nIterations; ++iter) {
74 actualIterations = iter + 1;
75
76 const int nActive = static_cast<int>(activeIdx.size());
77 if (nActive == 0)
78 break;
79
80 // Extract active columns of G^T*G and G^T*M
81 MatrixXd matGtG_active(nActive, nActive);
82 MatrixXd matGtM_active(nActive, nTimes);
83
84 for (int i = 0; i < nActive; ++i) {
85 matGtM_active.row(i) = matGtM.row(activeIdx[i]);
86 for (int j = 0; j < nActive; ++j) {
87 matGtG_active(i,j) = matGtG(activeIdx[i], activeIdx[j]);
88 }
89 }
90
91 // Build diagonal weight matrix W = diag(1/w_i^2)
92 VectorXd vecWdiag(nActive);
93 for (int i = 0; i < nActive; ++i) {
94 double w = vecWeights(activeIdx[i]);
95 vecWdiag(i) = 1.0 / (w * w);
96 }
97
98 // Solve (G^T*G + alpha*W) * X_active = G^T*M
99 MatrixXd matLhs = matGtG_active;
100 matLhs.diagonal() += alpha * vecWdiag;
101
102 MatrixXd matX_active = matLhs.ldlt().solve(matGtM_active);
103
104 // Write back to full solution
105 matX.setZero();
106 for (int i = 0; i < nActive; ++i) {
107 matX.row(activeIdx[i]) = matX_active.row(i);
108 }
109
110 // Update weights: w_i = max(||X_i||_2, 1e-10)
111 vecWeightsOld = vecWeights;
112 for (int i = 0; i < nSources; ++i) {
113 vecWeights(i) = std::max(matX.row(i).norm(), 1e-10);
114 }
115
116 // Active set pruning: keep sources with w_i >= 1e-8
117 std::vector<int> newActive;
118 newActive.reserve(nActive);
119 for (int i = 0; i < nSources; ++i) {
120 if (vecWeights(i) >= 1e-8) {
121 newActive.push_back(i);
122 }
123 }
124 activeIdx = newActive;
125
126 // Check convergence
127 double maxChange = 0.0;
128 for (int idx : activeIdx) {
129 maxChange = std::max(maxChange, std::abs(vecWeights(idx) - vecWeightsOld(idx)));
130 }
131 if (maxChange < tolerance)
132 break;
133 }
134
135 // Build result
136 InvMxneResult result;
137 result.nIterations = actualIterations;
138
139 // Collect active vertices and build sparse output
140 QVector<int> finalActive;
141 for (int i = 0; i < nSources; ++i) {
142 if (matX.row(i).norm() >= 1e-8) {
143 finalActive.append(i);
144 }
145 }
146 result.activeVertices = finalActive;
147
148 // Build source estimate with active rows only
149 const int nActiveFinal = finalActive.size();
150 MatrixXd matActiveSol(nActiveFinal, nTimes);
151 VectorXi vecActiveVerts(nActiveFinal);
152 for (int i = 0; i < nActiveFinal; ++i) {
153 matActiveSol.row(i) = matX.row(finalActive[i]);
154 vecActiveVerts(i) = finalActive[i];
155 }
156
157 result.stc = InvSourceEstimate(matActiveSol, vecActiveVerts, 0.0f, 1.0f);
159
160 // Compute residual norm ||M - G*X||_F
161 MatrixXd matResidual = matData - matGain * matX;
162 result.residualNorm = matResidual.norm();
163
164 return result;
165}
Mixed-Norm Estimate (MxNE) sparse inverse solver — block-sparse L21 minimisation for focal source rec...
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
QVector< int > activeVertices
Definition inv_mxne.h:60
InvSourceEstimate stc
Definition inv_mxne.h:59
static InvMxneResult compute(const Eigen::MatrixXd &matGain, const Eigen::MatrixXd &matData, double alpha, int nIterations=50, double tolerance=1e-6)
Definition inv_mxne.cpp:45