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()
93 /
static_cast<double>(n_epochs - 1));
94 VectorXd z = (y.array() - z_mean).matrix();
95 if (z_std > 1e-15) z /= z_std;
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())
105 /
static_cast<double>(Xc.cols());
109 C /=
static_cast<double>(n_epochs);
110 Cz /=
static_cast<double>(n_epochs);
113 GeneralizedSelfAdjointEigenSolver<MatrixXd> solver(Cz, C);
114 if (solver.info() != Eigen::Success) {
115 throw std::runtime_error(
116 "DecodingSpoc::fit: eigenvalue decomposition failed");
119 const VectorXd& all_evals = solver.eigenvalues();
120 const MatrixXd& all_evecs = solver.eigenvectors();
123 std::vector<int> idx(
static_cast<size_t>(n_ch));
124 std::iota(idx.begin(), idx.end(), 0);
125 std::sort(idx.begin(), idx.end(), [&](
int a,
int b) {
126 return std::abs(all_evals(a)) > std::abs(all_evals(b));
129 int n_comp = std::min(m_nComponents,
static_cast<int>(n_ch));
130 m_filters.resize(n_comp, n_ch);
132 for (
int i = 0; i < n_comp; ++i) {
133 int j = idx[
static_cast<size_t>(i)];
134 VectorXd w = all_evecs.col(j);
135 double norm = w.norm();
136 if (norm > 0.0) 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()
154 /
static_cast<double>(centered.size()));
165 throw std::runtime_error(
"DecodingSpoc::transform: not fitted");
169 const int nEpochs =
static_cast<int>(epochs.size());
170 const int nComp =
static_cast<int>(m_filters.rows());
171 const int nTimes =
static_cast<int>(epochs[0].cols());
173 MatrixXd result(nEpochs * nComp, nTimes);
174 for (
int e = 0; e < nEpochs; ++e) {
175 result.middleRows(
static_cast<Eigen::Index
>(e) * nComp, nComp) = m_filters * epochs[
static_cast<size_t>(e)];
181 MatrixXd
X = computePowerFeatures(epochs);
184 X =
X.array().max(1e-30).log().matrix();
186 for (
int c = 0; c <
X.cols(); ++c) {
188 if (s < 1e-15) s = 1.0;
189 X.col(c) = (
X.col(c).array() - m_mean(c)) / s;