This guide provides an overview of the RESPOND C++ API for developers wishing to use the library in their own projects.
Overview
The RESPOND library provides a flexible framework for building opioid use disorder models through composition of models, transitions, and history tracking. The core components are:
- Model: Abstract base class representing a state transition system
- Simulation: Aggregates and coordinates multiple models
- Transition: Abstract base for specific transition types
- History: Tracks state vectors over time
- TransitionFactory: Creates concrete transition instances
Core Concepts
State Vectors
Models operate on state vectors (Eigen::VectorXd) representing the population distribution across model states. A state vector element at index i represents the count of individuals in state i.
Transitions
Transitions apply transformations to state vectors using transition matrices. The RESPOND model supports several transition types:
- Migration: Population movement between states
- Behavior: Behavioral state changes
- Intervention: Intervention-driven state changes
- Overdose: Overdose-related transitions
- BackgroundDeath: Background mortality transitions
History Tracking
History objects record state vectors at each timestep, enabling analysis of state trajectories over time. Histories support sparse timesteps—gaps are automatically filled with zero vectors.
Model Class
The Model class is the abstract base for all models in RESPOND.
#include <respond/model.hpp>
Eigen::VectorXd initial_state(50);
initial_state.setZero();
model->SetState(initial_state);
transition->AddTransitionMatrix(some_matrix);
model->AddTransition(transition);
model->RunTransitions();
Eigen::VectorXd current_state = model->GetState();
auto histories = model->GetHistories();
static std::unique_ptr< Model > Create(const std::string &name, const std::string &log_name="console")
Factory method to create a Model instance.
static std::unique_ptr< Transition > CreateTransition(const std::string &type, const std::string &log_name)
Creates a transition of the specified type.
Key Methods
SetState(const Eigen::VectorXd &state): Sets the model's state vector (copied internally)
GetState() const: Returns a copy of the current state
RunTransitions(): Executes all registered transitions
AddTransition(const std::unique_ptr<Transition> &t): Adds a transition (assumes ownership)
GetTransitionNames() const: Returns names of all transitions
ClearTransitions(): Removes all transitions
GetHistories() const: Returns map of history name to History objects
CreateDefaultHistories(): Initializes default history tracking
SetHistories(const std::map<std::string, History> &h): Sets history records
GetModelName() const: Returns model name
GetLogName() const: Returns associated logger name
clone() const: Creates a deep copy of the model
Simulation Class
The Simulation class manages multiple models and coordinates their execution.
#include <respond/simulation.hpp>
sim.AddModel(model1);
sim.AddModel(model2);
sim.Run();
auto all_histories = sim.GetModelHistories();
auto model_names = sim.GetModelNames();
auto history_names = sim.GetModelHistoryNames();
Manages and executes multiple models in a coordinated simulation. A Simulation aggregates Model insta...
Definition: simulation.hpp:30
Key Methods
Run(): Executes one simulation step for all models
AddModel(const std::unique_ptr<Model> &model): Adds a model (cloned internally)
GetModels() const: Returns const reference to model vector
GetModelNames() const: Returns all model names
ClearModels(): Removes all models
GetModelHistories() const: Returns state histories for all models
GetModelHistoryNames() const: Returns (model_name, history_name) pairs
GetLogName() const: Returns logger name
History Class
The History class records and manages state vectors across timesteps.
#include <respond/history.hpp>
hist.AddState(state_vector_0, 0);
hist.AddState(state_vector_1, 1);
hist.AddState(state_vector_2, 2);
hist.AddState(another_state);
auto state_at_t0 = hist.GetStateMap()[0];
auto all_states = hist.GetStateAsVector();
std::string name = hist.GetHistoryName();
std::string log_name = hist.GetLogName();
hist.Clear();
Tracks and manages state vector history over time. History records state snapshots at discrete timest...
Definition: history.hpp:37
Key Methods
AddState(const Eigen::VectorXd &state, int timestep = -1): Records a state
- If timestep < 0, automatically assigns next available timestep
- If timestep already exists, currently overwrites
GetStateMap() const: Returns map of timestep → state vector
GetStateAsVector() const: Returns contiguous vector of states (fills gaps with zeros)
GetHistoryName() const: Returns history identifier
GetLogName() const: Returns logger name
Clear(): Removes all recorded states
operator==, operator!=: Comparison operators
Transition Class
The Transition class is abstract; use TransitionFactory to create concrete instances.
#include <respond/transition.hpp>
#include <respond/transition_factory.hpp>
"behavior",
"my_logger"
);
Eigen::MatrixXd trans_matrix = ...;
transition->AddTransitionMatrix(trans_matrix);
auto histories_map = ...;
Eigen::VectorXd result = transition->Execute(current_state, histories_map);
std::string name = transition->GetTransitionName();
std::string log = transition->GetLogName();
transition->ClearTransitionMatrices();
Supported Transition Types
| Type | Description |
| "migration" | Population migration transitions |
| "behavior" | Behavioral state changes |
| "intervention" | Intervention-driven transitions |
| "overdose" | Overdose-related transitions |
| "background_death" | Background mortality transitions |
Logging Integration
RESPOND uses the spdlog library for logging. Models and transitions accept a logger name:
respond::CreateFileLogger("my_logger", "path/to/logfile.log");
Complete Example
#include <respond/simulation.hpp>
#include <respond/model.hpp>
#include <respond/transition_factory.hpp>
#include <respond/logging.hpp>
int main() {
respond::CreateFileLogger("app", "simulation.log");
Eigen::VectorXd initial_state = Eigen::VectorXd::Zero(50);
initial_state(0) = 1000;
model->SetState(initial_state);
"behavior", "app");
model->AddTransition(behavior_transition);
"migration", "app");
model->AddTransition(migration_transition);
sim.AddModel(model);
for (int t = 0; t < 52; ++t) {
sim.Run();
}
auto histories = sim.GetModelHistories();
auto history_names = sim.GetModelHistoryNames();
return 0;
}
Memory Management
RESPOND uses std::unique_ptr for ownership management:
- Models and Transitions are typically managed by Simulation or parent objects
- History objects are copyable and can be freely copied
- All models are cloned when added to a Simulation (ownership transfer)
- Clearing containers (ClearModels, ClearTransitions) deletes contained objects
Best Practices
- Use TransitionFactory to create transitions—it handles type dispatch
- Let Simulation manage models for automatic cloning and lifecycle management
- Reuse History objects for multiple runs to accumulate results
- Use const references where available (GetState returns a copy for safety)
- Initialize loggers early before creating models to enable error tracking
- Validate matrix dimensions before adding to transitions (not checked by API)
Common Patterns
Running Multiple Independent Simulations
for (int run = 0; run < num_runs; ++run) {
sim.AddModel(model);
for (int t = 0; t < duration; ++t) {
sim.Run();
}
}
Resetting Model State
Eigen::VectorXd initial_state = ...;
model->SetState(initial_state);
model->ClearTransitions();
model->CreateDefaultHistories();
Copying Simulations
Parallel Execution with Shared Logging
When running multiple models in parallel, all loggers can safely write to the same file using RESPOND's shared sink functionality. This ensures thread-safe logging without file corruption.
Basic Parallel Logging Setup
#include <respond/logging.hpp>
#include <respond/model.hpp>
#include <thread>
#include <vector>
int main() {
respond::SetLogPattern(respond::LogPattern::kThreadSafe);
respond::SetFlushInterval(3);
respond::CreateSharedLogger("model_1");
respond::CreateSharedLogger("model_2");
respond::CreateSharedLogger("model_3");
return 0;
}
Running Models in Parallel with Unified Logging
#include <respond/logging.hpp>
#include <respond/simulation.hpp>
#include <thread>
#include <vector>
void RunSimulation(int id, const std::string& log_file) {
std::string logger_name = "model_" + std::to_string(id);
respond::CreateSharedLogger(logger_name);
Eigen::VectorXd initial_state = Eigen::VectorXd::Zero(50);
initial_state(0) = 1000;
model->SetState(initial_state);
sim.AddModel(model);
for (int t = 0; t < 52; ++t) {
sim.Run();
}
respond::FlushAllLoggers();
}
int main() {
respond::SetLogPattern(respond::LogPattern::kThreadSafe);
respond::SetFlushInterval(0);
const int num_threads = 4;
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(RunSimulation, i, "unified.log");
}
for (auto& t : threads) {
t.join();
}
return 0;
}
Shared Logger Pattern Options
The LogPattern enum controls log format for all shared loggers:
- **
kSimple**: Minimal format [logger_name] message
- **
kStandard**: Includes time and thread ID (default)
- **
kDetailed**: Full timestamp with milliseconds; best for debugging
- **
kThreadSafe**: Optimized for concurrent writes with sequence numbers
respond::SetLogPattern(respond::LogPattern::kDetailed);
auto current = respond::GetLogPattern();
std::string pattern_str = respond::LoggingConfig::GetPatternString(current);
Monitoring Shared Loggers
bool exists = (respond::CheckLoggerExists("model_1") == respond::CreationStatus::kExists);
std::string info = respond::GetLoggerInfo("model_1");
respond::SetLoggerLevel("model_1", spdlog::level::info);
respond::FlushAllLoggers();
Thread-Safe File Sink Management
The CreateSharedFileSink function creates file sinks that are automatically cached and reused:
auto sink = respond::CreateSharedFileSink("logs/simulation.log");
respond::CreateSharedLogger("logger_1");
respond::CreateSharedLogger("logger_2");
Best Practices for Parallel Logging
- Call
SetLogPattern() once at program startup, before creating any loggers
- Call
CreateSharedLogger() instead of CreateFileLogger() when using parallel execution
- Use
kThreadSafe pattern when logs will have high concurrent write volume
- Set
FlushInterval(0) for critical logging; use FlushInterval(3-5) for performance
- Call
FlushAllLoggers() at end of main before exit to ensure all writes complete
- Monitor logger levels with
GetLoggerInfo() when debugging multi-model runs
Troubleshooting
- Assertion failures: Ensure matrix dimensions match state vector size before adding to transitions
- Empty histories: Call
CreateDefaultHistories() after model setup or manually add histories
- Logger errors: Ensure logger names exist (create with
CreateFileLogger if needed)
- Memory issues: Verify no circular unique_ptr references; models own transitions
For more information, see the Doxygen-generated API documentation or the Architecture and Design guide.
Previous: Architecture and Design
Next: Data Guide