DiffEq - Modern C++ ODE Integration Library 1.0.0
High-performance C++ library for solving ODEs with async signal processing
Loading...
Searching...
No Matches
output_decorator.hpp
1#pragma once
2
3#include "integrator_decorator.hpp"
4#include <vector>
5#include <functional>
6#include <chrono>
7#include <fstream>
8#include <sstream>
9
10namespace diffeq::core::composable {
11
15enum class OutputMode {
16 ONLINE, // Real-time output during integration
17 OFFLINE, // Buffered output after integration
18 HYBRID // Combination of online and offline
19};
20
25 OutputMode mode{OutputMode::ONLINE};
26 std::chrono::microseconds output_interval{1000};
27 size_t buffer_size{1000};
28 bool enable_compression{false};
29 bool enable_file_output{false};
30 std::string output_filename;
31 bool append_to_file{false};
32
33 // Validation settings
34 bool validate_intervals{true};
35 std::chrono::microseconds min_output_interval{10}; // Minimum 10μs
36 std::chrono::microseconds max_output_interval{std::chrono::minutes{1}}; // Maximum 1 minute
37
42 void validate() const {
43 if (validate_intervals) {
44 if (output_interval < min_output_interval) {
45 throw std::invalid_argument("output_interval below minimum " +
46 std::to_string(min_output_interval.count()) + "μs");
47 }
48 if (output_interval > max_output_interval) {
49 throw std::invalid_argument("output_interval exceeds maximum " +
50 std::to_string(max_output_interval.count()) + "μs");
51 }
52 }
53
54 if (buffer_size == 0) {
55 throw std::invalid_argument("buffer_size must be positive");
56 }
57
58 if (enable_file_output && output_filename.empty()) {
59 throw std::invalid_argument("output_filename required when file output is enabled");
60 }
61 }
62};
63
68 size_t total_outputs{0};
69 size_t online_outputs{0};
70 size_t buffered_outputs{0};
71 size_t file_writes{0};
72 std::chrono::milliseconds total_output_time{0};
73 size_t buffer_overflows{0};
74
75 double average_output_time_ms() const {
76 return total_outputs > 0 ?
77 static_cast<double>(total_output_time.count()) / total_outputs : 0.0;
78 }
79};
80
96template<system_state S>
98private:
99 OutputConfig config_;
100 std::function<void(const S&, typename IntegratorDecorator<S>::time_type, size_t)> output_handler_;
101 std::vector<std::tuple<S, typename IntegratorDecorator<S>::time_type, size_t>> output_buffer_;
102 std::chrono::steady_clock::time_point last_output_;
103 size_t step_count_{0};
104 OutputStats stats_;
105 std::unique_ptr<std::ofstream> output_file_;
106
107public:
115 explicit OutputDecorator(std::unique_ptr<AbstractIntegrator<S>> integrator,
116 OutputConfig config = {},
117 std::function<void(const S&, typename IntegratorDecorator<S>::time_type, size_t)> handler = nullptr)
118 : IntegratorDecorator<S>(std::move(integrator))
119 , config_(std::move(config))
120 , output_handler_(std::move(handler))
121 , last_output_(std::chrono::steady_clock::now()) {
122
123 config_.validate();
124 initialize_file_output();
125 }
126
131 try {
132 flush_output();
133 if (output_file_ && output_file_->is_open()) {
134 output_file_->close();
135 }
136 } catch (...) {
137 // Swallow exceptions in destructor
138 }
139 }
140
144 void step(typename IntegratorDecorator<S>::state_type& state, typename IntegratorDecorator<S>::time_type dt) override {
145 this->wrapped_integrator_->step(state, dt);
146 ++step_count_;
147
148 handle_output(state, this->current_time());
149 }
150
154 void integrate(typename IntegratorDecorator<S>::state_type& state, typename IntegratorDecorator<S>::time_type dt,
155 typename IntegratorDecorator<S>::time_type end_time) override {
156 if (config_.mode == OutputMode::OFFLINE) {
157 // Just integrate and buffer final result
158 this->wrapped_integrator_->integrate(state, dt, end_time);
159 buffer_output(state, this->current_time(), step_count_);
160 } else {
161 // Step-by-step with online output
162 while (this->current_time() < end_time) {
163 typename IntegratorDecorator<S>::time_type step_size = std::min(dt, end_time - this->current_time());
164 this->step(state, step_size);
165 }
166 }
167
168 if (config_.mode == OutputMode::OFFLINE || config_.mode == OutputMode::HYBRID) {
169 flush_output();
170 }
171 }
172
177 void set_output_handler(std::function<void(const S&, typename IntegratorDecorator<S>::time_type, size_t)> handler) {
178 output_handler_ = std::move(handler);
179 }
180
185 const std::vector<std::tuple<S, typename IntegratorDecorator<S>::time_type, size_t>>& get_buffer() const {
186 return output_buffer_;
187 }
188
192 void clear_buffer() {
193 output_buffer_.clear();
194 stats_.buffered_outputs = 0;
195 }
196
201 if (output_handler_) {
202 auto start_time = std::chrono::high_resolution_clock::now();
203
204 for (const auto& [state, time, step] : output_buffer_) {
205 output_handler_(state, time, step);
206 stats_.total_outputs++;
207 }
208
209 auto end_time = std::chrono::high_resolution_clock::now();
210 stats_.total_output_time += std::chrono::duration_cast<std::chrono::milliseconds>(
211 end_time - start_time);
212 }
213
214 if (config_.enable_file_output && output_file_ && output_file_->is_open()) {
215 write_buffer_to_file();
216 }
217 }
218
223 return stats_;
224 }
225
230 stats_ = OutputStats{};
231 }
232
236 OutputConfig& config() { return config_; }
237 const OutputConfig& config() const { return config_; }
238
244 void update_config(OutputConfig new_config) {
245 new_config.validate();
246
247 // Check if file output settings changed
248 bool file_settings_changed = (new_config.enable_file_output != config_.enable_file_output) ||
249 (new_config.output_filename != config_.output_filename) ||
250 (new_config.append_to_file != config_.append_to_file);
251
252 config_ = std::move(new_config);
253
254 if (file_settings_changed) {
255 initialize_file_output();
256 }
257 }
258
259private:
263 void handle_output(const S& state, typename IntegratorDecorator<S>::time_type time) {
264 auto now = std::chrono::steady_clock::now();
265
266 if (config_.mode == OutputMode::ONLINE || config_.mode == OutputMode::HYBRID) {
267 if (now - last_output_ >= config_.output_interval) {
268 if (output_handler_) {
269 auto start_time = std::chrono::high_resolution_clock::now();
270 output_handler_(state, time, step_count_);
271 auto end_time = std::chrono::high_resolution_clock::now();
272
273 stats_.total_output_time += std::chrono::duration_cast<std::chrono::milliseconds>(
274 end_time - start_time);
275 stats_.total_outputs++;
276 stats_.online_outputs++;
277 }
278 last_output_ = now;
279 }
280 }
281
282 if (config_.mode == OutputMode::OFFLINE || config_.mode == OutputMode::HYBRID) {
283 buffer_output(state, time, step_count_);
284 }
285 }
286
290 void buffer_output(const S& state, typename IntegratorDecorator<S>::time_type time, size_t step) {
291 if (output_buffer_.size() >= config_.buffer_size) {
292 output_buffer_.erase(output_buffer_.begin());
293 stats_.buffer_overflows++;
294 }
295 output_buffer_.emplace_back(state, time, step);
296 stats_.buffered_outputs++;
297 }
298
302 void initialize_file_output() {
303 if (config_.enable_file_output && !config_.output_filename.empty()) {
304 output_file_ = std::make_unique<std::ofstream>();
305
306 std::ios_base::openmode mode = std::ios_base::out;
307 if (config_.append_to_file) {
308 mode |= std::ios_base::app;
309 }
310
311 output_file_->open(config_.output_filename, mode);
312
313 if (!output_file_->is_open()) {
314 throw std::runtime_error("Failed to open output file: " + config_.output_filename);
315 }
316
317 // Write header if creating new file
318 if (!config_.append_to_file) {
319 *output_file_ << "# DiffEq Integration Output\n";
320 *output_file_ << "# Time, State, Step\n";
321 }
322 }
323 }
324
328 void write_buffer_to_file() {
329 if (!output_file_ || !output_file_->is_open()) {
330 return;
331 }
332
333 for (const auto& [state, time, step] : output_buffer_) {
334 *output_file_ << time << ", ";
335
336 // Write state (assuming it's iterable)
337 bool first = true;
338 for (const auto& component : state) {
339 if (!first) *output_file_ << " ";
340 *output_file_ << component;
341 first = false;
342 }
343
344 *output_file_ << ", " << step << "\n";
345 stats_.file_writes++;
346 }
347
348 output_file_->flush();
349 }
350};
351
352} // namespace diffeq::core::composable
Base decorator interface for integrator enhancements.
Output decorator - adds configurable output to any integrator.
void step(typename IntegratorDecorator< S >::state_type &state, typename IntegratorDecorator< S >::time_type dt) override
Override step to add output handling.
void reset_statistics()
Reset output statistics.
void update_config(OutputConfig new_config)
Update output configuration with validation.
void integrate(typename IntegratorDecorator< S >::state_type &state, typename IntegratorDecorator< S >::time_type dt, typename IntegratorDecorator< S >::time_type end_time) override
Override integrate to handle different output modes.
void flush_output()
Force immediate output flush.
void set_output_handler(std::function< void(const S &, typename IntegratorDecorator< S >::time_type, size_t)> handler)
Set or change output handler function.
OutputConfig & config()
Access and modify output configuration.
OutputDecorator(std::unique_ptr< AbstractIntegrator< S > > integrator, OutputConfig config={}, std::function< void(const S &, typename IntegratorDecorator< S >::time_type, size_t)> handler=nullptr)
Construct output decorator.
void clear_buffer()
Clear the output buffer.
~OutputDecorator()
Destructor ensures proper cleanup and final output flush.
const std::vector< std::tuple< S, typename IntegratorDecorator< S >::time_type, size_t > > & get_buffer() const
Get current output buffer contents.
const OutputStats & get_statistics() const
Get output statistics.
Configuration for output handling.
void validate() const
Validate configuration parameters.
Output statistics and information.