This guide provides an overview of the RESPOND C++ API for developers wishing to use the library in their own projects.
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
- Timestep: Owns and sequences transitions for one simulation step
- Transition: Abstract base for specific transition types
- History: Tracks state vectors over time
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 apply transformations to state vectors using transition matrices or vectors. 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 objects record state vectors at timesteps, enabling analysis of state trajectories over time. Histories support sparse timesteps and can return contiguous vectors with zero-filled gaps.
The Model class is the abstract base for all models in RESPOND.
#include <respond/model.hpp>
#include <respond/timestep.hpp>
#include <respond/transition.hpp>
// Create a model
auto model = respond::Model::Create("markov", "logger_name");
// Set the initial state
Eigen::VectorXd initial_state(50);
initial_state.setZero();
model->SetState(initial_state);
// Build one timestep with transitions
respond::Timestep step("logger_name");
auto &behavior_transition = step.CreateTransition("behavior");
behavior_transition->AddMatrix(some_matrix);
auto migration_transition = respond::Transition::Create("migration");
step.AddTransition(migration_transition);
// Mutable index access to owned transition slots
step[0]->AddMatrix(some_other_matrix);
model->AddTimestep(step);
// Execute one simulation step
model->RunTimestep();
// Retrieve current state
Eigen::VectorXd current_state = model->GetState();
// Access history records
auto histories = model->GetHistories();SetState(const Eigen::Ref<const Eigen::VectorXd> &state): Sets the model's state vectorGetState() const: Returns a const Eigen ref to the current stateAddTimestep(const Timestep ×tep): Adds a timestep (deep-copied)RunTimestep(): Runs the current timestep and advances timeRunTimestep(size_t idx): Runs a specific timestep indexRunTimesteps(): Runs all registered timesteps (bounded by final timestep when set)ClearTimesteps(): Removes all timestepsGetHistories() const: Returns map of history name to History objectsCreateDefaultHistories(): Initializes default history trackingClearHistories(): Clears history records and resets history trackingGetName() const: Returns model nameclone() const: Creates a deep copy of the model
The Simulation class manages multiple models and coordinates their execution.
#include <respond/simulation.hpp>
// Create a simulation
respond::Simulation sim("my_logger");
// Add models
auto model1 = respond::Model::Create("model1", "my_logger");
auto model2 = respond::Model::Create("model2", "my_logger");
sim.AddModel(model1);
sim.AddModel(model2);
// Mutate owned models directly via index
sim[0]->CreateDefaultHistories();
sim[1]->CreateDefaultHistories();
// Run 52 timesteps for all models
sim.Run(52);
// Retrieve results
auto model_0_histories = sim.GetModelHistory(0);
auto model_names = sim.GetModelNames();
// Get history names for one model
auto history_names = sim.GetModelHistoryNames(0);Run(int duration = -1): Runs all models for the configured durationSetDuration(int duration): Sets default duration used byRun()when no argument is providedAddModel(const std::unique_ptr<Model> &model): Adds a model (cloned internally)operator[](size_t idx): Mutable index access to owned model slot (sim[idx]->Method())operator[](size_t idx) const: Const index access to owned modelGetModels() const: Returns a deep-copied vector of modelsGetModel(int idx) const: Returns one deep-copied model by index (-1returns last)GetModelIndexNameMap() const: Returns map of model index to model nameGetModelNames() const: Returns all model namesClearModels(): Removes all modelsGetModelHistory(size_t idx) const: Returns one model's history mapGetModelHistoryNames(size_t idx) const: Returns history names for one model
The Timestep class owns transitions for one model step and supports both transition creation and clone-based insertion.
#include <respond/timestep.hpp>
#include <respond/transition.hpp>
respond::Timestep step("my_logger");
// Build transition in-place
auto &behavior = step.CreateTransition("behavior");
behavior->AddMatrix(behavior_matrix);
// Add an existing transition by clone
auto migration = respond::Transition::Create("migration");
step.AddTransition(migration);
// Mutable slot access (in-place edits)
step[0]->AddMatrix(another_behavior_matrix);
// Replace a slot by cloning from another slot or transition pointer
step[1] = step[0];
step[1] = migration;
// Const slot access
const respond::Timestep &const_step = step;
const respond::Transition &t = const_step[0];CreateTransition(const std::string &transition_name): Creates and stores a transition by typeAddTransition(const std::unique_ptr<Transition> &transition): Clones and stores caller-provided transitionoperator[](size_t idx): Mutable slot access for transition mutation/replacementoperator[](size_t idx) const: Const transition reference by indexGetTransition(const size_t &idx) const: Gets transition pointer by indexGetTransition(const std::string &transition_name) const: Gets transition pointer by nameGetTransitionNames() const: Returns transition names in execution orderRemoveTransition(size_t idx): Removes and returns transition at index
sim[idx]accesses the model owned bySimulationand can be used for in-place mutation.sim[idx] = *other_modelreplaces the model atidxby cloningother_model.sim[idx] = other_model_ptrreplaces the model atidxby cloning the pointee (caller retains ownership).GetModels()andGetModel(...)return clones for safe detached access.- Name-based retrieval is not provided; use
GetModelIndexNameMap()to resolve names to indices.
The History class records and manages state vectors across timesteps.
#include <respond/history.hpp>
// Create a history
respond::History hist("population_states", "my_logger");
// Add states at specific timesteps
hist.AddState(state_vector_0, 0);
hist.AddState(state_vector_1, 1);
hist.AddState(state_vector_2, 2);
// Or let it auto-assign timesteps
hist.AddState(another_state); // Assigned to next available timestep
// Retrieve states
auto state_at_t0 = hist.GetStateMap()[0];
auto all_states = hist.GetStateAsVector(); // Contiguous vector, fills gaps
// Query history properties
std::string name = hist.GetName();
respond::HistoryMode mode = hist.GetHistoryMode();
// Clear history
hist.Clear();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 vectorGetRecordedTimesteps() const: Returns stored timesteps without densifyingGetRecordedStates() const: Returns stored states without densifyingGetStateAsVector() const: Returns contiguous vector of states (fills gaps with zeros)GetName() const: Returns history identifierGetLatestRecordedTimestep() const: Returns latest recorded timestepGetPendingState() const: Returns pending aggregate for accumulated historiesHasPendingState() const: Indicates pending aggregate stateClear(): Removes all recorded statesoperator==,operator!=: Comparison operators
The Transition class is abstract; use Transition::Create(...) to create concrete instances.
#include <respond/transition.hpp>
// Create a transition
auto transition = respond::Transition::Create(
"behavior", // Type
"behavior_name", // Instance name
"my_logger" // Logger name
);
// Add transformation matrices
Eigen::MatrixXd trans_matrix = ...;
transition->AddMatrix(trans_matrix);
// Execute the transition (typically done by model timesteps)
auto histories_map = ...; // From model
Eigen::VectorXd result = transition->Execute(current_state, histories_map);
// Get transition properties
std::string name = transition->GetName();
// Clear matrices
transition->ClearMatrices();| Type | Description |
|---|---|
| "migration" | Population migration transitions |
| "behavior" | Behavioral state changes |
| "intervention" | Intervention-driven transitions |
| "overdose" | Overdose-related transitions |
| "background_death" | Background mortality transitions |
RESPOND uses the spdlog library for logging. Models and transitions accept a logger name:
// All logging is handled by passing logger names
auto model = respond::Model::Create("my_model", "my_logger");
// The model will use this logger for any errors or warnings
// Create loggers separately using respond::CreateFileLogger
respond::CreateFileLogger("my_logger", "path/to/logfile.log");#include <respond/simulation.hpp>
#include <respond/model.hpp>
#include <respond/timestep.hpp>
#include <respond/logging.hpp>
int main() {
// Create logger
respond::CreateFileLogger("app", "simulation.log");
// Create simulation
respond::Simulation sim("app");
// Create and configure a model
auto model = respond::Model::Create("markov", "app");
// Set initial state (e.g., 1000 individuals across 50 states)
Eigen::VectorXd initial_state = Eigen::VectorXd::Zero(50);
initial_state(0) = 1000; // All in first state
model->SetState(initial_state);
// Create a reusable timestep with transitions
respond::Timestep step("app");
auto &behavior_transition = step.CreateTransition("behavior");
// behavior_transition->AddMatrix(...);
auto &migration_transition = step.CreateTransition("migration");
// migration_transition->AddMatrix(...);
// Register timesteps on the model
for (int t = 0; t < 52; ++t) {
model->AddTimestep(step);
}
// Add model to simulation
sim.AddModel(model);
// Configure the owned model through Simulation indexing
sim[0]->CreateDefaultHistories();
// Run simulation for 52 timesteps
sim.Run(52);
// Extract results
auto histories = sim.GetModelHistory(0);
auto history_names = sim.GetModelHistoryNames(0);
// Process results...
return 0;
}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
- Clearing containers (for example
ClearModels,ClearTimesteps) deletes contained objects
- Use
Transition::Createto create transitions by type. - Build timesteps explicitly and add them to models in execution order.
- Set simulation duration intentionally (
SetDurationorRun(duration)) to match timestep plans. - Initialize loggers early before creating models and transitions.
- Validate matrix dimensions and ranges before adding matrices.
for (int run = 0; run < num_runs; ++run) {
respond::Simulation sim("logger_" + std::to_string(run));
auto model = respond::Model::Create("markov", "logger_" + std::to_string(run));
// Configure model...
sim.AddModel(model);
sim.Run(duration);
// Store results...
}// To reset a model to initial state
Eigen::VectorXd initial_state = ...;
model->SetState(initial_state);
// To also clear history and timesteps
model->ClearTimesteps();
model->CreateDefaultHistories();respond::Simulation sim1("logger");
// ... configure sim1 ...
// Create independent copy
respond::Simulation sim2 = sim1; // All models are cloned
// Modifications to sim2 don't affect sim1When 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.
#include <respond/logging.hpp>
#include <respond/model.hpp>
#include <thread>
#include <vector>
int main() {
// Configure shared logging (all loggers write to same file)
respond::SetLogPattern(respond::LogPattern::kThreadSafe);
respond::SetFlushInterval(3); // Auto-flush every 3 seconds
// Create multiple loggers that share the same file sink
respond::CreateSharedLogger("model_1");
respond::CreateSharedLogger("model_2");
respond::CreateSharedLogger("model_3");
// Now multiple threads can safely write to shared log
return 0;
}#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);
// Create logger that uses shared sink
respond::CreateSharedLogger(logger_name);
// Create and run simulation
respond::Simulation sim(logger_name);
auto model = respond::Model::Create("markov", logger_name);
// Configure model...
Eigen::VectorXd initial_state = Eigen::VectorXd::Zero(50);
initial_state(0) = 1000;
model->SetState(initial_state);
// Add timesteps...
sim.AddModel(model);
// Run simulation
sim.Run(52);
// Flush logs for this model
respond::FlushAllLoggers();
}
int main() {
// Setup shared logging once
respond::SetLogPattern(respond::LogPattern::kThreadSafe);
respond::SetFlushInterval(0); // Flush immediately
const int num_threads = 4;
std::vector<std::thread> threads;
// Launch parallel simulations
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(RunSimulation, i, "unified.log");
}
// Wait for all to complete
for (auto& t : threads) {
t.join();
}
// All output safely written to unified.log
return 0;
}The LogPattern enum controls log format for all shared loggers:
kSimple: Minimal format[logger_name] messagekStandard: Includes time and thread ID (default)kDetailed: Full timestamp with milliseconds; best for debuggingkThreadSafe: Optimized for concurrent writes with sequence numbers
// Change pattern anytime
respond::SetLogPattern(respond::LogPattern::kDetailed);
// Query current pattern
auto current = respond::GetLogPattern();
// Get pattern as string for programmatic use
std::string pattern_str = respond::LoggingConfig::GetPatternString(current);// Check if logger exists
bool exists = (respond::CheckLoggerExists("model_1") == respond::CreationStatus::kExists);
// Get detailed logger information
std::string info = respond::GetLoggerInfo("model_1");
// Returns: "Logger: model_1\n Level: debug\n Sinks: 1"
// Set individual logger level
respond::SetLoggerLevel("model_1", spdlog::level::info);
// Flush all loggers immediately
respond::FlushAllLoggers();The CreateSharedFileSink function creates file sinks that are automatically cached and reused:
// Create or get cached sink for filepath
auto sink = respond::CreateSharedFileSink("logs/simulation.log");
// If called again with same path, returns existing sink (no duplicate file handles)
// Multiple loggers using same sink (no file conflicts)
respond::CreateSharedLogger("logger_1"); // Uses default sink
respond::CreateSharedLogger("logger_2"); // Uses same sink
// Both logger_1 and logger_2 write to same file safely- Call
SetLogPattern()once at program startup, before creating any loggers - Call
CreateSharedLogger()instead ofCreateFileLogger()when using parallel execution - Use
kThreadSafepattern when logs will have high concurrent write volume - Set
FlushInterval(0)for critical logging; useFlushInterval(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
- 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
CreateFileLoggerif 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