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 nSources = static_cast<int>(matGain.cols());
53 const int nTimes = static_cast<int>(matData.cols());
54
55 // Precompute G^T * G and G^T * M
56 MatrixXd matGtG = matGain.transpose() * matGain;
57 MatrixXd matGtM = matGain.transpose() * matData;
58
59 // Initialize weights to 1
60 VectorXd vecWeights = VectorXd::Ones(nSources);
61 VectorXd vecWeightsOld = vecWeights;
62
63 // Active set: all sources initially active
64 std::vector<int> activeIdx(nSources);
65 std::iota(activeIdx.begin(), activeIdx.end(), 0);
66
67 // Full source solution
68 MatrixXd matX = MatrixXd::Zero(nSources, nTimes);
69
70 int actualIterations = 0;
71
72 for (int iter = 0; iter < nIterations; ++iter) {
73 actualIterations = iter + 1;
74
75 const int nActive = static_cast<int>(activeIdx.size());
76 if (nActive == 0)
77 break;
78
79 // Extract active columns of G^T*G and G^T*M
80 MatrixXd matGtG_active(nActive, nActive);
81 MatrixXd matGtM_active(nActive, nTimes);
82
83 for (int i = 0; i < nActive; ++i) {
84 matGtM_active.row(i) = matGtM.row(activeIdx[i]);
85 for (int j = 0; j < nActive; ++j) {
86 matGtG_active(i, j) = matGtG(activeIdx[i], activeIdx[j]);
87 }
88 }
89
90 // Stationary point of the L21 objective: G^T (M - G X) = alpha X_i / ||X_i||, so W = diag(1/w_i)
91 VectorXd vecWdiag(nActive);
92 for (int i = 0; i < nActive; ++i) {
93 vecWdiag(i) = 1.0 / vecWeights(activeIdx[i]);
94 }
95
96 // Solve (G^T*G + alpha*W) * X_active = G^T*M
97 MatrixXd matLhs = matGtG_active;
98 matLhs.diagonal() += alpha * vecWdiag;
99
100 MatrixXd matX_active = matLhs.ldlt().solve(matGtM_active);
101
102 // Write back to full solution
103 matX.setZero();
104 for (int i = 0; i < nActive; ++i) {
105 matX.row(activeIdx[i]) = matX_active.row(i);
106 }
107
108 // Update weights: w_i = max(||X_i||_2, 1e-10)
109 vecWeightsOld = vecWeights;
110 for (int i = 0; i < nSources; ++i) {
111 vecWeights(i) = std::max(matX.row(i).norm(), 1e-10);
112 }
113
114 // Active set pruning: keep sources with w_i >= 1e-8
115 std::vector<int> newActive;
116 newActive.reserve(nActive);
117 for (int i = 0; i < nSources; ++i) {
118 if (vecWeights(i) >= 1e-8) {
119 newActive.push_back(i);
120 }
121 }
122 activeIdx = newActive;
123
124 // Check convergence
125 double maxChange = 0.0;
126 for (int idx : activeIdx) {
127 maxChange = std::max(maxChange, std::abs(vecWeights(idx) - vecWeightsOld(idx)));
128 }
129 if (maxChange < tolerance)
130 break;
131 }
132
133 // Build result
134 InvMxneResult result;
135 result.nIterations = actualIterations;
136
137 // Collect active vertices and build sparse output
138 QVector<int> finalActive;
139 for (int i = 0; i < nSources; ++i) {
140 if (matX.row(i).norm() >= 1e-8) {
141 finalActive.append(i);
142 }
143 }
144 result.activeVertices = finalActive;
145
146 // Build source estimate with active rows only
147 const int nActiveFinal = finalActive.size();
148 MatrixXd matActiveSol(nActiveFinal, nTimes);
149 VectorXi vecActiveVerts(nActiveFinal);
150 for (int i = 0; i < nActiveFinal; ++i) {
151 matActiveSol.row(i) = matX.row(finalActive[i]);
152 vecActiveVerts(i) = finalActive[i];
153 }
154
155 result.stc = InvSourceEstimate(matActiveSol, vecActiveVerts, 0.0f, 1.0f);
157
158 // Compute residual norm ||M - G*X||_F
159 MatrixXd matResidual = matData - matGain * matX;
160 result.residualNorm = matResidual.norm();
161
162 return result;
163}
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:63
InvSourceEstimate stc
Definition inv_mxne.h:62
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