Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
HDF5Recorder.hpp
Go to the documentation of this file.
1#pragma once
2
12
13#include <memory>
14#include <regex>
15#include <string>
16#include <vector>
17
21#include <vulcan/io/CSVExport.hpp>
22#include <vulcan/io/Frame.hpp>
23#include <vulcan/io/HDF5Reader.hpp>
24#include <vulcan/io/HDF5Writer.hpp>
25#include <vulcan/io/TelemetrySchema.hpp>
26
27namespace icarus {
28
29// Forward declaration for backwards compatibility
30struct HDF5RecorderConfig;
31
56class HDF5Recorder : public Recorder {
57 public:
63 HDF5Recorder(const SignalRegistry<double> &registry, const RecordingConfig &config)
64 : registry_(registry), config_(config) {}
65
70 void Open(const std::string &path) override {
71 if (!path.empty()) {
72 config_.path = path;
73 }
74 BuildSchema();
75 writer_ = std::make_unique<vulcan::io::HDF5Writer>(config_.path, schema_);
76 frame_ = std::make_unique<vulcan::io::Frame>(schema_);
77 frame_counter_ = 0; // Reset for new recording
78 }
79
86 void Record(double time) override {
87 if (!writer_) {
88 throw std::runtime_error("Recorder not open");
89 }
90
91 // Apply decimation: only record every N frames
92 ++frame_counter_;
93 if (config_.decimation > 1 && (frame_counter_ % config_.decimation) != 1) {
94 return; // Skip this frame
95 }
96
97 frame_->set_time(time);
98 CaptureSignals();
99 writer_->write_frame(*frame_);
100 }
101
107 void Close() override {
108 if (writer_) {
109 writer_->close();
110 writer_.reset();
111
112 // Export to CSV if configured
113 if (config_.export_csv) {
114 ExportCSV();
115 }
116 }
117 }
118
122 [[nodiscard]] size_t FrameCount() const { return writer_ ? writer_->frame_count() : 0; }
123
127 void Flush() {
128 if (writer_) {
129 writer_->flush();
130 }
131 }
132
136 [[nodiscard]] const vulcan::io::TelemetrySchema &Schema() const { return schema_; }
137
141 [[nodiscard]] const std::vector<std::string> &RecordedSignals() const {
142 return recorded_signals_;
143 }
144
145 private:
146 const SignalRegistry<double> &registry_;
147 RecordingConfig config_;
148 vulcan::io::TelemetrySchema schema_;
149 std::unique_ptr<vulcan::io::HDF5Writer> writer_;
150 std::unique_ptr<vulcan::io::Frame> frame_;
151 std::vector<std::string> recorded_signals_;
152 size_t frame_counter_ = 0;
153
157 void BuildSchema() {
158 schema_ = vulcan::io::TelemetrySchema();
159 recorded_signals_.clear();
160
161 // Compile include/exclude patterns for "signals" mode
162 std::vector<std::regex> include_re, exclude_re;
163 if (config_.mode == "signals" && !config_.include.empty()) {
164 for (const auto &pat : config_.include) {
165 include_re.emplace_back(pat);
166 }
167 }
168 for (const auto &pat : config_.exclude) {
169 exclude_re.emplace_back(pat);
170 }
171
172 // Iterate over all signals and filter based on mode
173 for (const auto &desc : registry_.GetDescriptors()) {
174 // Skip derivatives unless explicitly included
175 if (!config_.include_derivatives && desc.name.find("_dot") != std::string::npos) {
176 continue;
177 }
178
179 // Mode-based filtering
180 bool should_include = false;
181
182 if (config_.mode == "all") {
183 // Include everything (outputs, inputs if enabled, params, config)
184 if (desc.kind == SignalKind::Input) {
185 should_include = config_.include_inputs;
186 } else {
187 should_include = true;
188 }
189 } else if (config_.mode == "outputs") {
190 // Only outputs (default mode)
191 should_include =
192 (desc.kind == SignalKind::Output || desc.kind == SignalKind::Parameter);
193 } else if (config_.mode == "signals") {
194 // Pattern-based selection
195 if (include_re.empty()) {
196 should_include = true; // No patterns = include all
197 } else {
198 for (const auto &re : include_re) {
199 if (std::regex_search(desc.name, re)) {
200 should_include = true;
201 break;
202 }
203 }
204 }
205 }
206
207 if (!should_include) {
208 continue;
209 }
210
211 // Apply exclude patterns
212 bool excluded = false;
213 for (const auto &re : exclude_re) {
214 if (std::regex_search(desc.name, re)) {
215 excluded = true;
216 break;
217 }
218 }
219 if (excluded) {
220 continue;
221 }
222
223 // Add to schema based on type
224 switch (desc.type) {
225 case SignalType::Double:
226 schema_.add_double(desc.name, desc.lifecycle, desc.unit);
227 break;
228 case SignalType::Int32:
229 schema_.add_int32(desc.name, desc.lifecycle, desc.semantic);
230 break;
231 case SignalType::Int64:
232 schema_.add_int64(desc.name, desc.lifecycle);
233 break;
234 }
235
236 recorded_signals_.push_back(desc.name);
237 }
238 }
239
243 void CaptureSignals() {
244 for (const auto &name : recorded_signals_) {
245 const auto *desc = registry_.get_descriptor(name);
246 if (!desc || !desc->data_ptr) {
247 continue;
248 }
249
250 switch (desc->type) {
251 case SignalType::Double:
252 frame_->set(name, *static_cast<const double *>(desc->data_ptr));
253 break;
254 case SignalType::Int32:
255 frame_->set(name, *static_cast<const int32_t *>(desc->data_ptr));
256 break;
257 case SignalType::Int64:
258 frame_->set(name, *static_cast<const int64_t *>(desc->data_ptr));
259 break;
260 }
261 }
262 }
263
270 void ExportCSV() {
271 // Derive CSV path from HDF5 path
272 std::string csv_path = config_.path;
273 auto dot_pos = csv_path.rfind('.');
274 if (dot_pos != std::string::npos) {
275 csv_path = csv_path.substr(0, dot_pos) + ".csv";
276 } else {
277 csv_path += ".csv";
278 }
279
280 // Use Vulcan's CSV export
281 vulcan::io::CSVExportOptions options;
282 options.signals = recorded_signals_;
283 vulcan::io::export_to_csv(config_.path, csv_path, options);
284 }
285};
286
287} // namespace icarus
Signal Registry (Backplane) for Icarus.
Simulator and subsystem configuration structs.
void Flush()
Flush buffered data to disk.
Definition HDF5Recorder.hpp:127
HDF5Recorder(const SignalRegistry< double > &registry, const RecordingConfig &config)
Construct recorder from RecordingConfig.
Definition HDF5Recorder.hpp:63
void Record(double time) override
Record current signal values as a frame.
Definition HDF5Recorder.hpp:86
void Close() override
Close recording file.
Definition HDF5Recorder.hpp:107
size_t FrameCount() const
Get number of frames written.
Definition HDF5Recorder.hpp:122
const vulcan::io::TelemetrySchema & Schema() const
Get the generated schema.
Definition HDF5Recorder.hpp:136
const std::vector< std::string > & RecordedSignals() const
Get list of recorded signal names.
Definition HDF5Recorder.hpp:141
void Open(const std::string &path) override
Open recording file and initialize writer.
Definition HDF5Recorder.hpp:70
Simulation data recorder interface.
Definition Recorder.hpp:12
Central registry for all simulation signals.
Definition Registry.hpp:37
Definition AggregationTypes.hpp:13
@ Output
Dynamic output signal (Scalar, Vec3, etc.).
Definition Signal.hpp:77
@ Input
Input port (wired to an output).
Definition Signal.hpp:78
@ Parameter
Scalar-typed parameter (optimizable).
Definition Signal.hpp:79
HDF5 recording configuration.
Definition SimulatorConfig.hpp:503
std::vector< std::string > include
Include patterns (regex) - used when mode = "signals".
Definition SimulatorConfig.hpp:514
std::string mode
Recording mode: "off", "all", "outputs", "signals".
Definition SimulatorConfig.hpp:511