RESPOND 2.5.1
Researching Effective Strategies to Prevent Opioid Death
Loading...
Searching...
No Matches
history.hpp
1
2// File: history.hpp //
3// Project: respond //
4// Created Date: 2026-02-05 //
5// Author: Matthew Carroll //
6// ----- //
7// Last Modified: 2026-09-24 //
8// Modified By: Matthew Carroll //
9// ----- //
10// Copyright (c) 2026 Syndemics Lab at Boston Medical Center //
12#ifndef RESPOND_HISTORY_HPP_
13#define RESPOND_HISTORY_HPP_
14
15#include <respond/constants.hpp>
16#include <respond/logging.hpp>
17#include <respond/logging_config.hpp>
18
19#include <algorithm>
20#include <cstddef>
21#include <map>
22#include <stdexcept>
23#include <utility>
24#include <vector>
25
26#include <Eigen/Dense>
27
28namespace respond {
29
32enum class HistoryMode : int {
33 kSnapshot = 0, // Snapshot of state at each timestep
34 kAccumulated = 1, // Accumulated contributions over timesteps
35 kCount = 2 // Enum Counter
36};
37
42inline HistoryMode GetDefaultHistoryMode(const std::string &name) {
43 if (name == "intervention_admission" || name == "total_overdose" ||
44 name == "fatal_overdose" || name == "background_death") {
45 return HistoryMode::kAccumulated;
46 }
47 return HistoryMode::kSnapshot;
48}
49
54class History {
55public:
57 //
58 // Rule of Five: Copy and Move Semantics
59 //
61
65 : History("state", GetDefaultHistoryMode("state"), LoggingConfig{}) {}
66
70 History(const std::string &name)
71 : History(name, GetDefaultHistoryMode(name), LoggingConfig{}) {}
72
76 History(const std::string &name, const HistoryMode &mode)
77 : History(name, mode, LoggingConfig{}) {}
78
83 [[deprecated("Use History(name, mode, LoggingConfig) instead")]]
84 History(const std::string &name, const HistoryMode &mode,
85 const std::string &log_name)
86 : History(name, mode,
87 LoggingConfig{log_name, RESPOND_DEFAULT_LOG_FILE, false}) {}
88
92 [[deprecated("Use History(name, LoggingConfig) instead")]]
93 History(const std::string &name, const std::string &log_name)
94 : History(name, GetDefaultHistoryMode(name),
95 LoggingConfig{log_name, RESPOND_DEFAULT_LOG_FILE, false}) {}
96
102 [[deprecated("Use History(name, LoggingConfig) instead")]]
103 History(const std::string &name, const std::string &log_name,
104 const std::string &log_filepath)
105 : History(name, GetDefaultHistoryMode(name),
106 LoggingConfig{log_name, log_filepath, false}) {}
107
114 [[deprecated("Use History(name, mode, LoggingConfig) instead")]]
115 History(const std::string &name, const HistoryMode &mode,
116 const std::string &log_name, const std::string &log_filepath)
117 : History(name, mode, LoggingConfig{log_name, log_filepath, false}) {}
118
119 History(const std::string &name, const LoggingConfig &logging_config)
120 : History(name, GetDefaultHistoryMode(name), logging_config) {}
121
122 History(const std::string &name, const HistoryMode &mode,
123 const LoggingConfig &logging_config)
124 : _name(name), _mode(mode), _log_name(logging_config.logger_name),
125 _logging_config(logging_config) {
126 if (ConfigureLogger(_logging_config) == CreationStatus::kError) {
127 throw std::runtime_error(
128 "Error attempting to initialize history logger.");
129 }
130 }
131
133 ~History() = default;
134
137 History(const History &other) {
138 _timesteps = other.GetRecordedTimesteps();
139 _states = other.GetRecordedStates();
140 _name = other._name;
141 _log_name = other._log_name;
142 _logging_config = other._logging_config;
143 _mode = other._mode;
144 _pending_state = other.GetPendingState();
145 }
146
150 History &operator=(const History &other) {
151 if (this != &other) {
152 _timesteps = other.GetRecordedTimesteps();
153 _states = other.GetRecordedStates();
154 _name = other._name;
155 _log_name = other._log_name;
156 _logging_config = other._logging_config;
157 _mode = other._mode;
158 _pending_state = other.GetPendingState();
159 }
160 return *this;
161 }
162
166 History(History &&other) noexcept
167 : _timesteps(std::move(other._timesteps)),
168 _states(std::move(other._states)), _name(std::move(other._name)),
169 _log_name(std::move(other._log_name)),
170 _logging_config(std::move(other._logging_config)), _mode(other._mode),
171 _pending_state(std::move(other._pending_state)) {}
172
176 History &operator=(History &&other) noexcept {
177 if (this != &other) {
178 _timesteps = std::move(other._timesteps);
179 _states = std::move(other._states);
180 _name = std::move(other._name);
181 _log_name = std::move(other._log_name);
182 _logging_config = std::move(other._logging_config);
183 _mode = other._mode;
184 _pending_state = std::move(other._pending_state);
185 }
186 return *this;
187 }
188
190 //
191 // History Methods: State Vector Management
192 //
194
202 void AddState(const Eigen::Ref<const Eigen::VectorXd> &state,
203 int timestep = -1) {
204 if (timestep < 0) {
205 timestep = GetNextTimestep();
206 }
207
208 const auto insertion_point =
209 std::lower_bound(_timesteps.begin(), _timesteps.end(), timestep);
210 const auto index =
211 static_cast<size_t>(insertion_point - _timesteps.begin());
212 if (insertion_point != _timesteps.end() &&
213 *insertion_point == timestep) {
214 _states[index] = state;
215 return;
216 }
217
218 _timesteps.insert(insertion_point, timestep);
219 _states.insert(_states.begin() + static_cast<std::ptrdiff_t>(index),
220 state);
221 }
222
225 void AccumulateState(const Eigen::Ref<const Eigen::VectorXd> &state) {
226 if (_mode != HistoryMode::kAccumulated) {
227 LogWarning(_log_name, "AccumulateState called on non-accumulated "
228 "history, adding state instead: " +
229 _name);
230 AddState(state);
231 return;
232 }
233
234 if (_pending_state.size() == 0) {
235 _pending_state = state;
236 return;
237 }
238 _pending_state += state;
239 }
240
244 void FlushPendingState(int timestep, Eigen::Index state_size) {
245 if (_mode != HistoryMode::kAccumulated) {
246 LogInfo(_log_name,
247 "FlushPendingState called on non-accumulated history, "
248 "no pending state to flush: " +
249 _name);
250 return;
251 }
252
253 Eigen::VectorXd value;
254 if (_pending_state.size() > 0) {
255 value = _pending_state;
256 } else {
257 LogInfo(_log_name,
258 "FlushPendingState called with no pending state, "
259 "recording zero vector: " +
260 _name);
261 value = Eigen::VectorXd::Zero(state_size);
262 }
263
264 AddState(value, timestep);
265 _pending_state.resize(0);
266 }
267
269 void Clear() {
270 _timesteps.clear();
271 _states.clear();
272 _pending_state.resize(0);
273 }
274
277 bool HasPendingState() const { return _pending_state.size() > 0; }
278
280 //
281 // Getters and Setters for History Vectors
282 //
284
287 std::map<int, Eigen::VectorXd> GetStateMap() const {
288 std::map<int, Eigen::VectorXd> state_map;
289 for (size_t index = 0; index < _timesteps.size(); ++index) {
290 state_map[_timesteps[index]] = _states[index];
291 }
292 return state_map;
293 }
294
297 const std::vector<int> &GetRecordedTimesteps() const { return _timesteps; }
298
301 const std::vector<Eigen::VectorXd> &GetRecordedStates() const {
302 return _states;
303 }
304
307 HistoryMode GetHistoryMode() const { return _mode; }
308
311 Eigen::VectorXd GetPendingState() const { return _pending_state; }
312
316 if (_timesteps.empty()) {
317 LogWarning(_log_name,
318 "GetLatestRecordedTimestep called on empty history: " +
319 _name);
320 return -1;
321 }
322 return _timesteps.back();
323 }
324
327 std::string GetName() const { return _name; }
328
333 std::vector<Eigen::VectorXd> GetStateAsVector() const {
334 std::vector<Eigen::VectorXd> ret;
335 if (_states.empty()) {
336 LogWarning(_log_name,
337 "GetStateAsVector called on empty history: " + _name);
338 return {};
339 }
340 int default_size = _states.front().size();
341 int tstep = 0;
342 for (size_t index = 0; index < _timesteps.size(); ++index) {
343 const int recorded_timestep = _timesteps[index];
344 const auto &recorded_state = _states[index];
345 while (recorded_timestep > tstep) {
346 ret.push_back(GetZeroVector(default_size));
347 tstep++;
348 }
349 ret.push_back(recorded_state);
350 tstep++;
351 }
352 return ret;
353 }
354
356 //
357 // Operator Comparisons and Stream Output
358 //
360
364 bool operator==(const History &other) const {
365 return _name == other._name && _log_name == other._log_name &&
366 _logging_config.logger_name ==
367 other._logging_config.logger_name &&
368 _logging_config.file_path == other._logging_config.file_path &&
369 _logging_config.use_shared_sink ==
370 other._logging_config.use_shared_sink &&
371 _mode == other._mode && GetStateMap() == other.GetStateMap() &&
372 GetPendingState().isApprox(other.GetPendingState());
373 }
374
378 bool operator!=(const History &other) const { return !(*this == other); }
379
380 friend std::ostream &operator<<(std::ostream &os, const History &history) {
381 os << "History(name=" << history._name << ", timesteps=[";
382 for (const auto &t : history._timesteps) {
383 os << t << ",";
384 }
385 os << "], pending_state=" << history._pending_state.transpose() << ")";
386 return os;
387 }
388
389private:
391 std::string _log_name;
393 LoggingConfig _logging_config;
395 std::string _name;
397 HistoryMode _mode;
399 std::vector<int> _timesteps;
401 std::vector<Eigen::VectorXd> _states;
403 Eigen::VectorXd _pending_state;
404
408 int GetNextTimestep() {
409 if (_timesteps.empty()) {
410 return 0;
411 }
412 return _timesteps.back() + 1;
413 }
414
418 Eigen::VectorXd GetZeroVector(const int &size) const {
419 return Eigen::VectorXd::Zero(size);
420 }
421};
422} // namespace respond
423
424#endif // RESPOND_HISTORY_HPP_
Tracks and manages state vector history over time. History records state snapshots at discrete timest...
Definition: history.hpp:54
History(const std::string &name, const std::string &log_name)
Constructs a history with a specified name and logger.
Definition: history.hpp:93
std::string GetName() const
Retrieves the identifier name of this history.
Definition: history.hpp:327
History & operator=(const History &other)
Copy assignment operator implementing the Rule of Five.
Definition: history.hpp:150
Eigen::VectorXd GetPendingState() const
Retrieves the pending accumulated state.
Definition: history.hpp:311
const std::vector< Eigen::VectorXd > & GetRecordedStates() const
Retrieves the recorded state vectors without densifying gaps.
Definition: history.hpp:301
History(const std::string &name, const HistoryMode &mode)
Constructs a history with a specified name and mode.
Definition: history.hpp:76
History(const std::string &name, const std::string &log_name, const std::string &log_filepath)
Constructs a history with a specified name, logger, and log file path.
Definition: history.hpp:103
History & operator=(History &&other) noexcept
Move assignment operator implementing the Rule of Five.
Definition: history.hpp:176
~History()=default
Destructor (default).
History(const std::string &name, const HistoryMode &mode, const std::string &log_name)
Constructs a history with a specified name, mode, and logger.
Definition: history.hpp:84
History()
Default constructor initializing a history with the default name "state" and default mode based on th...
Definition: history.hpp:64
void Clear()
Clears all recorded state history.
Definition: history.hpp:269
History(const std::string &name)
Constructs a history with a specified name, using the default mode based on that name.
Definition: history.hpp:70
History(const History &other)
Copy constructor implementing the Rule of Five. Creates an independent copy of the history state and ...
Definition: history.hpp:137
int GetLatestRecordedTimestep() const
Retrieves the latest recorded timestep.
Definition: history.hpp:315
std::map< int, Eigen::VectorXd > GetStateMap() const
Retrieves the complete state map (timestep -> state vector).
Definition: history.hpp:287
History(const std::string &name, const HistoryMode &mode, const std::string &log_name, const std::string &log_filepath)
Constructs a history with a specified name, mode, logger, and log file path.
Definition: history.hpp:115
std::vector< Eigen::VectorXd > GetStateAsVector() const
Converts the sparse history map to a contiguous vector of states. Gaps in timesteps are filled with z...
Definition: history.hpp:333
bool operator!=(const History &other) const
Inequality comparison operator.
Definition: history.hpp:378
History(History &&other) noexcept
Move constructor implementing the Rule of Five.
Definition: history.hpp:166
void FlushPendingState(int timestep, Eigen::Index state_size)
Flushes pending accumulated state into a recorded timestep.
Definition: history.hpp:244
HistoryMode GetHistoryMode() const
Retrieves the configured history recording mode.
Definition: history.hpp:307
bool HasPendingState() const
Indicates whether an accumulated history has pending state.
Definition: history.hpp:277
void AccumulateState(const Eigen::Ref< const Eigen::VectorXd > &state)
Adds a contribution to an accumulated history.
Definition: history.hpp:225
void AddState(const Eigen::Ref< const Eigen::VectorXd > &state, int timestep=-1)
Records a state vector at a specific or automatic timestep.
Definition: history.hpp:202
bool operator==(const History &other) const
Equality comparison operator.
Definition: history.hpp:364
const std::vector< int > & GetRecordedTimesteps() const
Retrieves the recorded timesteps without densifying gaps.
Definition: history.hpp:297
Describes the logging destination used by a library object.
Definition: logging_config.hpp:21