78 throw std::invalid_argument(
"DecodingSpoc::fit: epochs must be non-empty");
80 if (
static_cast<int>(epochs.size()) != y.size()) {
81 throw std::invalid_argument(
82 "DecodingSpoc::fit: epochs and y must have the same length");
86 const auto n_epochs =
static_cast<Index
>(epochs.size());
87 const auto n_ch = epochs[0].rows();
90 double z_mean = y.mean();
91 double z_std = std::sqrt(
92 (y.array() - z_mean).square().sum() /
static_cast<double>(n_epochs - 1));
93 VectorXd z = (y.array() - z_mean).matrix();
98 MatrixXd C = MatrixXd::Zero(n_ch, n_ch);
99 MatrixXd Cz = MatrixXd::Zero(n_ch, n_ch);
101 for (Index e = 0; e < n_epochs; ++e) {
102 const auto&
X = epochs[
static_cast<size_t>(e)];
103 MatrixXd Xc =
X.colwise() -
X.rowwise().mean();
104 MatrixXd cov_e = (Xc * Xc.transpose()) /
static_cast<double>(Xc.cols());
108 C /=
static_cast<double>(n_epochs);
109 Cz /=
static_cast<double>(n_epochs);
112 GeneralizedSelfAdjointEigenSolver<MatrixXd> solver(Cz, C);
113 if (solver.info() != Eigen::Success) {
114 throw std::runtime_error(
115 "DecodingSpoc::fit: eigenvalue decomposition failed");
118 const VectorXd& all_evals = solver.eigenvalues();
119 const MatrixXd& all_evecs = solver.eigenvectors();
122 std::vector<int> idx(
static_cast<size_t>(n_ch));
123 std::iota(idx.begin(), idx.end(), 0);
124 std::sort(idx.begin(), idx.end(), [&](
int a,
int b) {
125 return std::abs(all_evals(a)) > std::abs(all_evals(b));
128 int n_comp = std::min(m_nComponents,
static_cast<int>(n_ch));
129 m_filters.resize(n_comp, n_ch);
131 for (
int i = 0; i < n_comp; ++i) {
132 int j = idx[
static_cast<size_t>(i)];
133 VectorXd w = all_evecs.col(j);
134 double norm = w.norm();
137 m_filters.row(i) = w.transpose();
141 MatrixXd Wt = m_filters.transpose();
142 MatrixXd CW = C * Wt;
143 MatrixXd WtCW = Wt.transpose() * CW;
144 m_patterns = (CW * WtCW.inverse());
147 MatrixXd powerFeatures = computePowerFeatures(epochs);
148 m_mean = powerFeatures.colwise().mean();
150 m_std = VectorXd(powerFeatures.cols());
151 for (
int c = 0; c < powerFeatures.cols(); ++c) {
152 VectorXd centered = powerFeatures.col(c).array() - m_mean(c);
153 m_std(c) = std::sqrt(centered.squaredNorm() /
static_cast<double>(centered.size()));
164 throw std::runtime_error(
"DecodingSpoc::transform: not fitted");
168 const int nEpochs =
static_cast<int>(epochs.size());
169 const int nComp =
static_cast<int>(m_filters.rows());
170 const int nTimes =
static_cast<int>(epochs[0].cols());
172 MatrixXd result(nEpochs * nComp, nTimes);
173 for (
int e = 0; e < nEpochs; ++e) {
174 result.middleRows(
static_cast<Eigen::Index
>(e) * nComp, nComp) = m_filters * epochs[
static_cast<size_t>(e)];
180 MatrixXd
X = computePowerFeatures(epochs);
183 X =
X.array().max(1e-30).log().matrix();
185 for (
int c = 0; c <
X.cols(); ++c) {
189 X.col(c) = (
X.col(c).array() - m_mean(c)) / s;