Skip to content

Commit ffa248d

Browse files
nijawareneSchmmknaranjaHenrZu
authored
953 Implement basic stochastic equation based model (#954)
- Implemented stochastic version of SIR and SIRS model, with specialized Simulations. - Implemented Euler-Maruyama method reusing EulerIntegratorCore and a custom advance function. - Time step dt is now updated during integration. This is currently only used by the new SDE models. - Protected IntegratorCore for stochastic models to avoid unintended use of ODE integrators. - Small fixes: avoid two dangling references, avoid a copy in FlowSimulation. Co-authored-by: reneSchm <49305466+reneSchm@users.noreply.github.com> Co-authored-by: Martin J. Kühn <62713180+mknaranja@users.noreply.github.com> Co-authored-by: HenrZu <henrik.zunker@dlr.de>
1 parent ce75c58 commit ffa248d

27 files changed

+1617
-41
lines changed

cpp/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ if(MEMILIO_BUILD_MODELS)
124124
add_subdirectory(models/ide_seir)
125125
add_subdirectory(models/ode_seir)
126126
add_subdirectory(models/ode_sir)
127+
add_subdirectory(models/sde_sir)
128+
add_subdirectory(models/sde_sirs)
127129
endif()
128130

129131
if(MEMILIO_BUILD_EXAMPLES)

cpp/examples/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,19 @@ target_compile_options(ode_seir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNI
2020

2121
add_executable(ode_sir_example ode_sir.cpp)
2222
target_link_libraries(ode_sir_example PRIVATE memilio ode_sir)
23+
target_compile_options(ode_sir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
24+
25+
add_executable(sde_sir_example sde_sir.cpp)
26+
target_link_libraries(sde_sir_example PRIVATE memilio sde_sir)
27+
target_compile_options(sde_sir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
28+
29+
add_executable(sde_sirs_example sde_sirs.cpp)
30+
target_link_libraries(sde_sirs_example PRIVATE memilio sde_sirs)
31+
target_compile_options(sde_sirs_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
2332

2433
add_executable(seir_flows_example ode_seir_flows.cpp)
2534
target_link_libraries(seir_flows_example PRIVATE memilio ode_seir)
35+
target_compile_options(seir_flows_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
2636

2737
add_executable(ode_secir_example ode_secir.cpp)
2838
target_link_libraries(ode_secir_example PRIVATE memilio ode_secir)

cpp/examples/ode_sir.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
* limitations under the License.
1919
*/
2020

21-
#include "ode_sir/model.h"
22-
#include "ode_sir/infection_state.h"
23-
#include "ode_sir/parameters.h"
2421
#include "memilio/compartments/simulation.h"
22+
#include "memilio/math/euler.h"
2523
#include "memilio/utils/logging.h"
24+
#include "ode_sir/infection_state.h"
25+
#include "ode_sir/model.h"
26+
#include "ode_sir/parameters.h"
2627

2728
int main()
2829
{

cpp/examples/sde_sir.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (C) 2020-2024 MEmilio
3+
*
4+
* Authors: Nils Wassmuth, Rene Schmieding, Martin J. Kuehn
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
#include "memilio/utils/logging.h"
22+
#include "sde_sir/model.h"
23+
#include "sde_sir/simulation.h"
24+
25+
int main()
26+
{
27+
mio::set_log_level(mio::LogLevel::debug);
28+
29+
double t0 = 0.;
30+
double tmax = 5.;
31+
double dt = 0.1;
32+
33+
double total_population = 10000;
34+
35+
mio::log_info("Simulating SIR; t={} ... {} with dt = {}.", t0, tmax, dt);
36+
37+
mio::ssir::Model model;
38+
39+
model.populations[{mio::Index<mio::ssir::InfectionState>(mio::ssir::InfectionState::Infected)}] = 100;
40+
model.populations[{mio::Index<mio::ssir::InfectionState>(mio::ssir::InfectionState::Recovered)}] = 1000;
41+
model.populations[{mio::Index<mio::ssir::InfectionState>(mio::ssir::InfectionState::Susceptible)}] =
42+
total_population -
43+
model.populations[{mio::Index<mio::ssir::InfectionState>(mio::ssir::InfectionState::Infected)}] -
44+
model.populations[{mio::Index<mio::ssir::InfectionState>(mio::ssir::InfectionState::Recovered)}];
45+
model.parameters.set<mio::ssir::TimeInfected>(10);
46+
model.parameters.set<mio::ssir::TransmissionProbabilityOnContact>(1);
47+
model.parameters.get<mio::ssir::ContactPatterns>().get_baseline()(0, 0) = 2.7;
48+
model.parameters.get<mio::ssir::ContactPatterns>().add_damping(0.6, mio::SimulationTime(12.5));
49+
50+
model.check_constraints();
51+
52+
auto sir = mio::ssir::simulate(t0, tmax, dt, model);
53+
54+
sir.print_table();
55+
}

cpp/examples/sde_sirs.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (C) 2020-2024 MEmilio
3+
*
4+
* Authors: Nils Wassmuth, Rene Schmieding, Martin J. Kuehn
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
#include "memilio/utils/logging.h"
22+
#include "sde_sirs/model.h"
23+
#include "sde_sirs/simulation.h"
24+
25+
int main()
26+
{
27+
mio::set_log_level(mio::LogLevel::debug);
28+
29+
double t0 = 0.;
30+
double tmax = 5.;
31+
double dt = 0.001;
32+
33+
double total_population = 10000;
34+
35+
mio::log_info("Simulating SIR; t={} ... {} with dt = {}.", t0, tmax, dt);
36+
37+
mio::ssirs::Model model;
38+
39+
model.populations[{mio::Index<mio::ssirs::InfectionState>(mio::ssirs::InfectionState::Infected)}] = 100;
40+
model.populations[{mio::Index<mio::ssirs::InfectionState>(mio::ssirs::InfectionState::Recovered)}] = 1000;
41+
model.populations[{mio::Index<mio::ssirs::InfectionState>(mio::ssirs::InfectionState::Susceptible)}] =
42+
total_population -
43+
model.populations[{mio::Index<mio::ssirs::InfectionState>(mio::ssirs::InfectionState::Infected)}] -
44+
model.populations[{mio::Index<mio::ssirs::InfectionState>(mio::ssirs::InfectionState::Recovered)}];
45+
model.parameters.set<mio::ssirs::TimeInfected>(10);
46+
model.parameters.set<mio::ssirs::TimeImmune>(100);
47+
model.parameters.set<mio::ssirs::TransmissionProbabilityOnContact>(1);
48+
model.parameters.get<mio::ssirs::ContactPatterns>().get_baseline()(0, 0) = 20.7;
49+
model.parameters.get<mio::ssirs::ContactPatterns>().add_damping(0.6, mio::SimulationTime(12.5));
50+
51+
model.check_constraints();
52+
53+
auto ssirs = mio::ssirs::simulate(t0, tmax, dt, model);
54+
55+
ssirs.print_table({"Susceptible", "Infected", "Recovered"});
56+
}

cpp/memilio/compartments/flow_simulation.h

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class FlowSimulation : public Simulation<M>
3737

3838
/**
3939
* @brief Set up the simulation with an ODE solver.
40-
* @param[in] model An instance of a compartmental model.
40+
* @param[in] model An instance of a flow model.
4141
* @param[in] t0 Start time.
4242
* @param[in] dt Initial step size of integration.
4343
*/
@@ -60,8 +60,8 @@ class FlowSimulation : public Simulation<M>
6060
assert(m_flow_result.get_num_time_points() == this->get_result().get_num_time_points());
6161
auto result = this->get_ode_integrator().advance(
6262
[this](auto&& flows, auto&& t, auto&& dflows_dt) {
63-
auto pop_result = this->get_result();
64-
auto model = this->get_model();
63+
const auto& pop_result = this->get_result();
64+
const auto& model = this->get_model();
6565
// compute current population
6666
// flows contains the accumulated outflows of each compartment for each target compartment at time t.
6767
// Using that the ODEs are linear expressions of the flows, get_derivatives can compute the total change
@@ -105,7 +105,7 @@ class FlowSimulation : public Simulation<M>
105105
}
106106
/** @} */
107107

108-
private:
108+
protected:
109109
/**
110110
* @brief Computes the distribution of the Population to the InfectionState%s based on the simulated flows.
111111
* Uses the same method as the DerivFunction used in advance to compute the population given the flows and initial
@@ -128,18 +128,22 @@ class FlowSimulation : public Simulation<M>
128128
}
129129

130130
Eigen::VectorXd m_pop; ///< pre-allocated temporary, used in right_hand_side()
131+
132+
private:
131133
mio::TimeSeries<ScalarType> m_flow_result; ///< flow result of the simulation
132134
};
133135

134136
/**
135-
* @brief simulate simulates a compartmental model and returns the same results as simulate and also the flows.
136-
* @param[in] t0 start time
137-
* @param[in] tmax end time
138-
* @param[in] dt initial step size of integration
139-
* @param[in] model: An instance of a compartmental model
140-
* @return a Tuple of two TimeSeries to represent the final simulation result and flows
141-
* @tparam Model a compartment model type
142-
* @tparam Sim a simulation type that can simulate the model.
137+
* @brief Run a FlowSimulation of a FlowModel.
138+
* @param[in] t0 Start time.
139+
* @param[in] tmax End time.
140+
* @param[in] dt Initial step size of integration.
141+
* @param[in] model An instance of a FlowModel.
142+
* @param[in] integrator Optionally override the IntegratorCore used by the FlowSimulation.
143+
* @return The simulation result as two TimeSeries. The first describes the compartments at each time point,
144+
* the second gives the corresponding flows that lead from t0 to each time point.
145+
* @tparam Model The particular Model derived from FlowModel to simulate.
146+
* @tparam Sim A FlowSimulation that can simulate the model.
143147
*/
144148
template <class Model, class Sim = FlowSimulation<Model>>
145149
std::vector<TimeSeries<ScalarType>> simulate_flows(double t0, double tmax, double dt, Model const& model,

cpp/memilio/compartments/simulation.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
#include "memilio/utils/metaprogramming.h"
2626
#include "memilio/math/stepper_wrapper.h"
2727
#include "memilio/utils/time_series.h"
28-
#include "memilio/math/euler.h"
2928

3029
namespace mio
3130
{
@@ -189,14 +188,15 @@ using is_compartment_model_simulation =
189188
is_compartment_model<typename Sim::Model>::value)>;
190189

191190
/**
192-
* @brief simulate simulates a compartmental model
193-
* @param[in] t0 start time
194-
* @param[in] tmax end time
195-
* @param[in] dt initial step size of integration
196-
* @param[in] model: An instance of a compartmental model
197-
* @return a TimeSeries to represent the final simulation result
198-
* @tparam Model a compartment model type
199-
* @tparam Sim a simulation type that can simulate the model.
191+
* @brief Run a Simulation of a CompartmentalModel.
192+
* @param[in] t0 Start time.
193+
* @param[in] tmax End time.
194+
* @param[in] dt Initial step size of integration.
195+
* @param[in] model An instance of a CompartmentalModel.
196+
* @param[in] integrator Optionally override the IntegratorCore used by the Simulation.
197+
* @return A TimeSeries to represent the final Simulation result
198+
* @tparam Model The particular Model derived from CompartmentModel to simulate.
199+
* @tparam Sim A Simulation that can simulate the model.
200200
*/
201201
template <class Model, class Sim = Simulation<Model>>
202202
TimeSeries<ScalarType> simulate(double t0, double tmax, double dt, Model const& model,

cpp/memilio/epidemiology/dynamic_npis.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ std::vector<size_t> get_damping_indices(const DampingExpr& damping_expr, Damping
217217
{
218218
std::vector<size_t> indices;
219219
for (size_t i = 0; i < damping_expr.get_dampings().size(); ++i) {
220-
auto& d = damping_expr.get_dampings()[i];
220+
const auto d = damping_expr.get_dampings()[i];
221221
if (d.get_level() == lvl && d.get_type() == type && double(d.get_time()) > double(begin) &&
222222
double(d.get_time()) < double(end)) {
223223
indices.push_back(i);

cpp/memilio/io/mobility_io.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ IOResult<void> write_graph(const Graph<Model, MigrationParameters>& graph, const
9696
//one file for the model (parameters and population)
9797
for (auto inode = size_t(0); inode < graph.nodes().size(); ++inode) {
9898
//node
99-
auto& node = graph.nodes()[inode];
99+
const auto node = graph.nodes()[inode];
100100
BOOST_OUTCOME_TRY(js_node_model, serialize_json(node.property, ioflags));
101101
Json::Value js_node(Json::objectValue);
102102
js_node["NodeId"] = node.id;
@@ -133,7 +133,8 @@ IOResult<void> write_graph(const Graph<Model, MigrationParameters>& graph, const
133133
* @param read_edges boolean value that decides whether the edges of the graph should also be read in.
134134
*/
135135
template <class Model>
136-
IOResult<Graph<Model, MigrationParameters>> read_graph(const std::string& directory, int ioflags = IOF_None, bool read_edges = true)
136+
IOResult<Graph<Model, MigrationParameters>> read_graph(const std::string& directory, int ioflags = IOF_None,
137+
bool read_edges = true)
137138
{
138139
std::string abs_path;
139140
if (!file_exists(directory, abs_path)) {
@@ -160,7 +161,7 @@ IOResult<Graph<Model, MigrationParameters>> read_graph(const std::string& direct
160161
}
161162

162163
//read edges; nodes must already be available for that)
163-
if(read_edges){
164+
if (read_edges) {
164165
for (auto inode = size_t(0); inode < graph.nodes().size(); ++inode) {
165166
//list of edges
166167
auto edge_filename = path_join(abs_path, "GraphEdges_node" + std::to_string(inode) + ".json");
@@ -177,7 +178,7 @@ IOResult<Graph<Model, MigrationParameters>> read_graph(const std::string& direct
177178
if (end_node_idx >= graph.nodes().size()) {
178179
log_error("EndNodeIndex not in range of number of graph nodes.");
179180
return failure(StatusCode::OutOfRange,
180-
edge_filename + ", EndNodeIndex not in range of number of graph nodes.");
181+
edge_filename + ", EndNodeIndex not in range of number of graph nodes.");
181182
}
182183
BOOST_OUTCOME_TRY(parameters, deserialize_json(e["Parameters"], Tag<MigrationParameters>{}, ioflags));
183184
graph.add_edge(start_node_idx, end_node_idx, parameters);

cpp/memilio/math/integrator.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
#include "memilio/math/integrator.h"
2121
#include "memilio/utils/logging.h"
22+
#include <cstddef>
2223

2324
namespace mio
2425
{
@@ -30,32 +31,34 @@ Eigen::Ref<Eigen::VectorXd> OdeIntegrator::advance(const DerivFunction& f, const
3031
assert(tmax > t0);
3132
assert(dt > 0);
3233

33-
const size_t nb_steps = (int)(ceil((tmax - t0) / dt)); // estimated number of time steps (if equidistant)
34+
const size_t num_steps =
35+
static_cast<size_t>(ceil((tmax - t0) / dt)); // estimated number of time steps (if equidistant)
3436

35-
results.reserve(results.get_num_time_points() + nb_steps);
37+
results.reserve(results.get_num_time_points() + num_steps);
3638

3739
bool step_okay = true;
3840

39-
double t = t0;
40-
size_t i = results.get_num_time_points() - 1;
41+
double dt_restore = 0; // used to restore dt if dt was decreased to reach tmax
42+
double t = t0;
43+
size_t i = results.get_num_time_points() - 1;
4144
while (std::abs((tmax - t) / (tmax - t0)) > 1e-10) {
4245
//we don't make timesteps too small as the error estimator of an adaptive integrator
4346
//may not be able to handle it. this is very conservative and maybe unnecessary,
4447
//but also unlikely to happen. may need to be reevaluated
4548

46-
auto dt_eff = std::min(dt, tmax - t);
49+
if (dt > tmax - t) {
50+
dt_restore = dt;
51+
dt = tmax - t;
52+
}
4753
results.add_time_point();
48-
step_okay &= m_core->step(f, results[i], t, dt_eff, results[i + 1]);
54+
step_okay &= m_core->step(f, results[i], t, dt, results[i + 1]);
4955
results.get_last_time() = t;
5056

5157
++i;
52-
53-
if (std::abs((tmax - t) / (tmax - t0)) > 1e-10 || dt_eff > dt) {
54-
//store dt only if it's not the last step as it is probably smaller than required for tolerances
55-
//except if the step function returns a bigger step size so as to not lose efficiency
56-
dt = dt_eff;
57-
}
5858
}
59+
// if dt was decreased to reach tmax in the last time iteration,
60+
// we restore it as it is now probably smaller than required for tolerances
61+
dt = std::max(dt, dt_restore);
5962

6063
if (!step_okay) {
6164
log_warning("Adaptive step sizing failed. Forcing an integration step of size dt_min.");

0 commit comments

Comments
 (0)