@@ -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)
121121void 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
269271void 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+
430486ScalarType 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