49 m_nChannels =
static_cast<int>(data.rows());
52 p = selectOrderBIC(data);
55 fitLevinsonDurbin(data, p);
83 QVector<MatrixXcd> vecH;
84 vecH.reserve(
static_cast<int>(freqs.size()));
86 const int nCh = m_nChannels;
87 const MatrixXcd matI = MatrixXcd::Identity(nCh, nCh);
88 const std::complex<double> j(0.0, 1.0);
90 for(
int fi = 0; fi < freqs.size(); ++fi) {
91 MatrixXcd matA = matI;
93 for(
int k = 0; k < m_order; ++k) {
94 const double phase = -2.0 *
M_PI * freqs(fi) * (k + 1);
95 const std::complex<double> expVal = std::exp(j * phase);
96 matA -= m_coeffs[k].cast<std::complex<double>>() * expVal;
99 vecH.append(matA.inverse());
110 QVector<MatrixXcd> vecS;
111 vecS.reserve(vecH.size());
113 const MatrixXcd matSigma = m_noiseCov.cast<std::complex<double>>();
115 for(
int fi = 0; fi < vecH.size(); ++fi) {
116 vecS.append(vecH[fi] * matSigma * vecH[fi].adjoint());
124void MvarModel::fitLevinsonDurbin(
const MatrixXd& data,
int p)
127 m_nChannels =
static_cast<int>(data.rows());
129 const int nCh = m_nChannels;
130 const int nSamples =
static_cast<int>(data.cols());
131 const int nObs = nSamples - p;
134 qWarning() <<
"MvarModel::fitLevinsonDurbin - Not enough samples for model order" << p;
136 m_noiseCov = MatrixXd::Identity(nCh, nCh);
141 MatrixXd dataCentered = data;
142 for(
int i = 0; i < nCh; ++i) {
143 dataCentered.row(i).array() -= dataCentered.row(i).mean();
152 MatrixXd matY = dataCentered.rightCols(nObs);
153 MatrixXd matZ(nCh * p, nObs);
155 for(
int k = 0; k < p; ++k) {
156 matZ.middleRows(
static_cast<Eigen::Index
>(k) * nCh, nCh) = dataCentered.middleCols(p - 1 - k, nObs);
160 MatrixXd matZZT = matZ * matZ.transpose();
161 MatrixXd matYZT = matY * matZ.transpose();
162 MatrixXd matA = matYZT * matZZT.ldlt().solve(MatrixXd::Identity(nCh * p, nCh * p));
167 for(
int k = 0; k < p; ++k) {
168 m_coeffs.append(matA.middleCols(k * nCh, nCh));
172 MatrixXd matE = matY - matA * matZ;
173 m_noiseCov = (matE * matE.transpose()) /
static_cast<double>(nObs);
178int MvarModel::selectOrderBIC(
const MatrixXd& data,
int maxOrder)
const
180 const int nCh =
static_cast<int>(data.rows());
181 const int nSamples =
static_cast<int>(data.cols());
184 maxOrder = qMin(maxOrder, nSamples / (nCh + 1));
190 MatrixXd dataCentered = data;
191 for(
int i = 0; i < nCh; ++i) {
192 dataCentered.row(i).array() -= dataCentered.row(i).mean();
195 double bestBIC = std::numeric_limits<double>::max();
198 for(
int p = 1; p <= maxOrder; ++p) {
199 const int nObs = nSamples - p;
200 if(nObs <= nCh * p) {
205 MatrixXd matY = dataCentered.rightCols(nObs);
206 MatrixXd matZ(nCh * p, nObs);
208 for(
int k = 0; k < p; ++k) {
209 matZ.middleRows(
static_cast<Eigen::Index
>(k) * nCh, nCh) = dataCentered.middleCols(p - 1 - k, nObs);
213 MatrixXd matZZT = matZ * matZ.transpose();
214 MatrixXd matYZT = matY * matZ.transpose();
215 MatrixXd matA = matYZT * matZZT.ldlt().solve(MatrixXd::Identity(nCh * p, nCh * p));
218 MatrixXd matE = matY - matA * matZ;
219 MatrixXd matSigma = (matE * matE.transpose()) /
static_cast<double>(nObs);
222 double detSigma = matSigma.determinant();
223 if(detSigma <= 0.0) {
227 const int nParams = p * nCh * nCh;
228 double bic = nObs * std::log(detSigma) + nParams * std::log(
static_cast<double>(nObs));
Multivariate autoregressive (MVAR) model fit and its frequency-domain decomposition; backbone of the ...
Functional connectivity metrics (coherence, PLV, cross-correlation, etc.).
Eigen::MatrixXd noiseCov() const
QVector< Eigen::MatrixXcd > spectralMatrix(const Eigen::VectorXd &freqs) const
QVector< Eigen::MatrixXcd > transferFunction(const Eigen::VectorXd &freqs) const
void fit(const Eigen::MatrixXd &data, int p=0)
QVector< Eigen::MatrixXd > coefficients() const