// Panel: N (possibly ragged) series stored contiguously (CSR layout), plus an aligned dense view when the series // share a common time index. The CSR layout is what the batched CPU/Metal kernels consume directly. #pragma once #include #include #include #include #include "raid/core/types.hpp" #include "raid/core/error.hpp" namespace raid { struct Panel { std::vector ids; // n_series std::vector offsets; // n_series + 1, offsets into t / v std::vector t; // concatenated timestamps std::vector v; // concatenated values usize n_series() const { return ids.size(); } usize total_size() const { return v.size(); } usize series_size(usize i) const { return static_cast(offsets[i + 1] - offsets[i]); } std::span values(usize i) const { return std::span(v).subspan(static_cast(offsets[i]), series_size(i)); } std::span times(usize i) const { return std::span(t).subspan(static_cast(offsets[i]), series_size(i)); } void push_back(std::string id, std::vector ts, std::vector vs) { RAID_REQUIRE(ts.size() == vs.size(), invalid_argument, "Panel::push_back size mismatch"); if (offsets.empty()) offsets.push_back(0); ids.push_back(std::move(id)); t.insert(t.end(), ts.begin(), ts.end()); v.insert(v.end(), vs.begin(), vs.end()); offsets.push_back(static_cast(v.size())); } bool is_rectangular() const { if (n_series() == 0) return true; const usize m = series_size(0); for (usize i = 1; i < n_series(); ++i) if (series_size(i) != m) return false; return true; } /// Dense T × N column-major matrix (requires rectangular panel). Eigen::MatrixXd dense() const { RAID_REQUIRE(is_rectangular(), invalid_argument, "Panel::dense requires equal-length series"); const usize T = n_series() ? series_size(0) : 0; Eigen::MatrixXd m(static_cast(T), static_cast(n_series())); for (usize j = 0; j < n_series(); ++j) { auto s = values(j); for (usize i = 0; i < T; ++i) m(static_cast(i), static_cast(j)) = s[i]; } return m; } }; } // namespace raid