Skip to content

Commit 0962eb0

Browse files
1117 reduce use of get support max method in IDE model to reduce run time (#1129)
- Store support_max for all TransitionDistributions in a vector - Compute derivative and contribution of TransitionDistributions to force of infection term once at the beginning of the simulation and store them in a vector instead of recomputing these in every time step to reduce run time Co-authored-by: lenaploetzke <[email protected]>
1 parent 125e1d2 commit 0962eb0

5 files changed

Lines changed: 163 additions & 58 deletions

File tree

cpp/models/ide_secir/model.cpp

Lines changed: 108 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ Model::Model(TimeSeries<ScalarType>&& init, ScalarType N_init, ScalarType deaths
4040
, m_N{N_init}
4141
{
4242
if (m_transitions.get_num_time_points() > 0) {
43-
// Add first time point in m_populations according to last time point in m_transitions which is where we start the simulation.
43+
// Add first time point in m_populations according to last time point in m_transitions which is where we start
44+
// the simulation.
4445
m_populations.add_time_point<Eigen::VectorXd>(
4546
m_transitions.get_last_time(), TimeSeries<ScalarType>::Vector::Constant((int)InfectionState::Count, 0.));
4647
}
@@ -64,12 +65,11 @@ void Model::compute_compartment_from_flows(ScalarType dt, Eigen::Index idx_Infec
6465
ScalarType calc_time = 0;
6566
// Determine relevant calculation area and corresponding index.
6667
if ((1 - parameters.get<TransitionProbabilities>()[idx_TransitionDistribution1]) > 0) {
67-
calc_time =
68-
std::max(parameters.get<TransitionDistributions>()[idx_TransitionDistribution1].get_support_max(dt, m_tol),
69-
parameters.get<TransitionDistributions>()[idx_TransitionDistribution2].get_support_max(dt, m_tol));
68+
calc_time = std::max(m_transitiondistributions_support_max[idx_TransitionDistribution1],
69+
m_transitiondistributions_support_max[idx_TransitionDistribution2]);
7070
}
7171
else {
72-
calc_time = parameters.get<TransitionDistributions>()[idx_TransitionDistribution1].get_support_max(dt, m_tol);
72+
calc_time = m_transitiondistributions_support_max[idx_TransitionDistribution1];
7373
}
7474

7575
Eigen::Index calc_time_index = (Eigen::Index)std::ceil(calc_time / dt) - 1;
@@ -121,7 +121,8 @@ void Model::initial_compute_compartments_infection(ScalarType dt)
121121
void Model::initial_compute_compartments(ScalarType dt)
122122
{
123123
// The initialization method only affects the Susceptible and Recovered compartments.
124-
// It is possible to calculate the sizes of the other compartments in advance because only the initial values of the flows are used.
124+
// It is possible to calculate the sizes of the other compartments in advance because only the initial values of
125+
// the flows are used.
125126
initial_compute_compartments_infection(dt);
126127

127128
if (m_total_confirmed_cases > 1e-12) {
@@ -158,8 +159,8 @@ void Model::initial_compute_compartments(ScalarType dt)
158159
m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Dead)];
159160
}
160161
else if (m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Recovered)] > 1e-12) {
161-
// If value for Recovered is initialized and standard method is not applicable (i.e., no value for total infections
162-
// or Susceptibles given directly), calculate Susceptibles via other compartments.
162+
// If value for Recovered is initialized and standard method is not applicable (i.e., no value for total
163+
// infections or Susceptibles given directly), calculate Susceptibles via other compartments.
163164
m_initialization_method = 3;
164165

165166
m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Susceptible)] =
@@ -188,7 +189,8 @@ void Model::initial_compute_compartments(ScalarType dt)
188189
m_transitions.get_last_value()[Eigen::Index(InfectionTransition::SusceptibleToExposed)] /
189190
(dt * m_forceofinfection);
190191

191-
// Recovered; calculated as the difference between the total population and the sum of the other compartment sizes.
192+
// Recovered; calculated as the difference between the total population and the sum of the other
193+
// compartment sizes.
192194
m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Recovered)] =
193195
m_N - m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Susceptible)] -
194196
m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Exposed)] -
@@ -207,8 +209,10 @@ void Model::initial_compute_compartments(ScalarType dt)
207209
}
208210

209211
// Verify that the initialization produces an appropriate result.
210-
// Another check would be if the sum of the compartments is equal to N, but in all initialization methods one of the compartments is initialized via N - the others.
211-
// This also means that if a compartment is greater than N, we will always have one or more compartments less than zero.
212+
// Another check would be if the sum of the compartments is equal to N, but in all initialization methods one of
213+
// the compartments is initialized via N - the others.
214+
// This also means that if a compartment is greater than N, we will always have one or more compartments less than
215+
// zero.
212216
// Check if all compartments are non negative.
213217
for (int i = 0; i < (int)InfectionState::Count; i++) {
214218
if (m_populations[0][i] < 0) {
@@ -236,24 +240,22 @@ void Model::compute_flow(Eigen::Index idx_InfectionTransitions, Eigen::Index idx
236240
Eigen::Index current_time_index)
237241
{
238242
ScalarType sum = 0;
239-
/* In order to satisfy TransitionDistribution(dt*i) = 0 for all i >= k, k is determined by the maximum support of the distribution.
243+
/* In order to satisfy TransitionDistribution(dt*i) = 0 for all i >= k, k is determined by the maximum support of
244+
the distribution.
240245
Since we are using a backwards difference scheme to compute the derivative, we have that the
241246
derivative of TransitionDistribution(dt*i) = 0 for all i >= k+1.
242247
243-
Hence calc_time_index goes until std::ceil(support_max/dt) since for std::ceil(support_max/dt)+1 all terms are already zero.
248+
Hence calc_time_index goes until std::ceil(support_max/dt) since for std::ceil(support_max/dt)+1 all terms are
249+
already zero.
244250
This needs to be adjusted if we are changing the finite difference scheme */
245251

246-
Eigen::Index calc_time_index = (Eigen::Index)std::ceil(
247-
parameters.get<TransitionDistributions>()[idx_InfectionTransitions].get_support_max(dt, m_tol) / dt);
252+
Eigen::Index calc_time_index =
253+
(Eigen::Index)std::ceil(m_transitiondistributions_support_max[idx_InfectionTransitions] / dt);
248254

249255
for (Eigen::Index i = current_time_index - calc_time_index; i < current_time_index; i++) {
250-
// (current_time_index - i) * dt is the time the individuals have already spent in this state.
251-
ScalarType state_age = (current_time_index - i) * dt;
252-
253-
// Use backward difference scheme to approximate first derivative.
254-
sum += (parameters.get<TransitionDistributions>()[idx_InfectionTransitions].eval(state_age) -
255-
parameters.get<TransitionDistributions>()[idx_InfectionTransitions].eval(state_age - dt)) /
256-
dt * m_transitions[i + 1][idx_IncomingFlow];
256+
// (current_time_index - i) is the index corresponding to time the individuals have already spent in this state.
257+
sum += m_transitiondistributions_derivative[idx_InfectionTransitions][current_time_index - i] *
258+
m_transitions[i + 1][idx_IncomingFlow];
257259
}
258260

259261
m_transitions.get_value(current_time_index)[Eigen::Index(idx_InfectionTransitions)] =
@@ -268,7 +270,8 @@ void Model::compute_flow(Eigen::Index idx_InfectionTransitions, Eigen::Index idx
268270

269271
void Model::flows_current_timestep(ScalarType dt)
270272
{
271-
// Calculate flow SusceptibleToExposed with force of infection from previous time step and Susceptibles from current time step.
273+
// Calculate flow SusceptibleToExposed with force of infection from previous time step and Susceptibles from
274+
// current time step.
272275
m_transitions.get_last_value()[Eigen::Index(InfectionTransition::SusceptibleToExposed)] =
273276
dt * m_forceofinfection * m_populations.get_last_value()[Eigen::Index(InfectionState::Susceptible)];
274277

@@ -360,15 +363,11 @@ void Model::compute_forceofinfection(ScalarType dt, bool initialization)
360363
m_forceofinfection = 0;
361364

362365
// Determine the relevant calculation area = union of the supports of the relevant transition distributions.
363-
ScalarType calc_time = std::max(
364-
{parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms]
365-
.get_support_max(dt, m_tol),
366-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedNoSymptomsToRecovered]
367-
.get_support_max(dt, m_tol),
368-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedSymptomsToInfectedSevere]
369-
.get_support_max(dt, m_tol),
370-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedSymptomsToRecovered]
371-
.get_support_max(dt, m_tol)});
366+
ScalarType calc_time =
367+
std::max({m_transitiondistributions_support_max[(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms],
368+
m_transitiondistributions_support_max[(int)InfectionTransition::InfectedNoSymptomsToRecovered],
369+
m_transitiondistributions_support_max[(int)InfectionTransition::InfectedSymptomsToInfectedSevere],
370+
m_transitiondistributions_support_max[(int)InfectionTransition::InfectedSymptomsToRecovered]});
372371

373372
// Corresponding index.
374373
// Need calc_time_index timesteps in sum,
@@ -397,38 +396,99 @@ void Model::compute_forceofinfection(ScalarType dt, bool initialization)
397396
}
398397

399398
for (Eigen::Index i = num_time_points - 1 - calc_time_index; i < num_time_points - 1; i++) {
400-
401-
ScalarType state_age = (num_time_points - 1 - i) * dt;
399+
Eigen::Index state_age_index = num_time_points - 1 - i;
400+
ScalarType state_age = state_age_index * dt;
402401
ScalarType season_val =
403402
1 + parameters.get<Seasonality>() *
404403
sin(3.141592653589793 * ((parameters.get<StartDay>() + current_time) / 182.5 + 0.5));
404+
405405
m_forceofinfection +=
406406
season_val * parameters.get<TransmissionProbabilityOnContact>().eval(state_age) *
407407
parameters.get<ContactPatterns>().get_cont_freq_mat().get_matrix_at(current_time)(0, 0) *
408-
((parameters
409-
.get<TransitionProbabilities>()[(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms] *
410-
parameters
411-
.get<TransitionDistributions>()[(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms]
412-
.eval(state_age) +
413-
parameters.get<TransitionProbabilities>()[(int)InfectionTransition::InfectedNoSymptomsToRecovered] *
414-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedNoSymptomsToRecovered]
415-
.eval(state_age)) *
408+
(m_transitiondistributions_in_forceofinfection[0][num_time_points - i - 1] *
416409
m_transitions[i + 1][Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms)] *
417410
parameters.get<RelativeTransmissionNoSymptoms>().eval(state_age) +
418-
(parameters.get<TransitionProbabilities>()[(int)InfectionTransition::InfectedSymptomsToInfectedSevere] *
419-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedSymptomsToInfectedSevere]
420-
.eval(state_age) +
421-
parameters.get<TransitionProbabilities>()[(int)InfectionTransition::InfectedSymptomsToRecovered] *
422-
parameters.get<TransitionDistributions>()[(int)InfectionTransition::InfectedSymptomsToRecovered].eval(
423-
state_age)) *
411+
m_transitiondistributions_in_forceofinfection[1][num_time_points - i - 1] *
424412
m_transitions[i + 1][Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms)] *
425413
parameters.get<RiskOfInfectionFromSymptomatic>().eval(state_age));
426414
}
427415
m_forceofinfection = 1 / (m_N - deaths) * m_forceofinfection;
428416
}
429417

418+
void Model::set_transitiondistributions_support_max(ScalarType dt)
419+
{
420+
// We do not consider the transition SusceptibleToExposed as it is not needed in the computations.
421+
for (int transition = 1; transition < (int)InfectionTransition::Count; transition++) {
422+
m_transitiondistributions_support_max[transition] =
423+
parameters.get<TransitionDistributions>()[transition].get_support_max(dt, m_tol);
424+
}
425+
}
426+
427+
void Model::set_transitiondistributions_derivative(ScalarType dt)
428+
{
429+
// We do not consider the transition SusceptibleToExposed as it is not needed in the computations.
430+
for (int transition = 1; transition < (int)InfectionTransition::Count; transition++) {
431+
Eigen::Index support_max_index =
432+
(Eigen::Index)std::ceil(m_transitiondistributions_support_max[transition] / dt);
433+
// Create vec_derivative that contains the value of the approximated derivative for all necessary time points.
434+
// Here, we evaluate the derivative at time points t_0, ..., t_{support_max_index}.
435+
std::vector<ScalarType> vec_derivative(support_max_index + 1, 0.);
436+
437+
for (Eigen::Index i = 0; i <= support_max_index; i++) {
438+
// Compute state_age for considered index.
439+
ScalarType state_age = (ScalarType)i * dt;
440+
// Compute derivative.
441+
vec_derivative[i] = (parameters.get<TransitionDistributions>()[transition].eval(state_age) -
442+
parameters.get<TransitionDistributions>()[transition].eval(state_age - dt)) /
443+
dt;
444+
}
445+
m_transitiondistributions_derivative[transition] = vec_derivative;
446+
}
447+
}
448+
449+
void Model::set_transitiondistributions_in_forceofinfection(ScalarType dt)
450+
{
451+
// Relevant transitions for force of infection term.
452+
std::vector<std::vector<int>> relevant_transitions = {
453+
{(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms,
454+
(int)InfectionTransition::InfectedNoSymptomsToRecovered},
455+
{(int)InfectionTransition::InfectedSymptomsToInfectedSevere,
456+
(int)InfectionTransition::InfectedSymptomsToRecovered}};
457+
458+
// Determine the relevant calculation area = union of the supports of the relevant transition distributions.
459+
ScalarType calc_time = std::max({m_transitiondistributions_support_max[relevant_transitions[0][0]],
460+
m_transitiondistributions_support_max[relevant_transitions[0][1]],
461+
m_transitiondistributions_support_max[relevant_transitions[1][0]],
462+
m_transitiondistributions_support_max[relevant_transitions[1][1]]});
463+
464+
// Corresponding index.
465+
// Need to evaluate survival functions at t_0, ..., t_{calc_time_index} for computation of force of infection,
466+
// subtract 1 because in the last summand all TransitionDistributions evaluate to 0 (by definition of support_max).
467+
Eigen::Index calc_time_index = (Eigen::Index)std::ceil(calc_time / dt) - 1;
468+
469+
// Compute contributions from survival functions and transition probabilities starting in InfectedNoSymptoms and
470+
// InfectedSymptoms, respectively, on force of infection term.
471+
for (int contribution = 0; contribution < 2; contribution++) {
472+
std::vector<ScalarType> vec_contribution_to_foi(calc_time_index + 1, 0.);
473+
for (Eigen::Index i = 0; i <= calc_time_index; i++) {
474+
// Compute state_age for all indices from t_0, ..., t_{calc_time_index}.
475+
ScalarType state_age = i * dt;
476+
vec_contribution_to_foi[i] =
477+
parameters.get<TransitionProbabilities>()[relevant_transitions[contribution][0]] *
478+
parameters.get<TransitionDistributions>()[relevant_transitions[contribution][0]].eval(state_age) +
479+
parameters.get<TransitionProbabilities>()[relevant_transitions[contribution][1]] *
480+
parameters.get<TransitionDistributions>()[relevant_transitions[contribution][1]].eval(state_age);
481+
}
482+
m_transitiondistributions_in_forceofinfection[contribution] = vec_contribution_to_foi;
483+
}
484+
}
485+
430486
ScalarType Model::get_global_support_max(ScalarType dt) const
431487
{
488+
// Note that this function computes the global_support_max via the get_support_max() method and does not make use
489+
// of the vector m_transitiondistributions_support_max. This is because the global_support_max is already used in
490+
// check_constraints and we cannot ensure that the vector has already been computed when checking for constraints
491+
// (which usually happens before setting the initial flows and simulating).
432492
return std::max(
433493
{parameters.get<TransitionDistributions>()[(int)InfectionTransition::ExposedToInfectedNoSymptoms]
434494
.get_support_max(dt, m_tol),

0 commit comments

Comments
 (0)