Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

677-computation-of-the-effective-reproduction-number-in-the-secir-model #698

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3285184
Added reproduction number files to the branch
johapau Jul 13, 2023
6cca6ba
Tested the secir reproduction number calculation
johapau Jul 14, 2023
46af975
Upper-lowercase changes
johapau Jul 14, 2023
58048f2
changed tests, added namespace
johapau Jul 24, 2023
af35572
changed secir-model.h and included methods to compute reproduction
johapau Jul 27, 2023
7fc41d5
added test for secir-reprod numbers
johapau Jul 27, 2023
1674b76
changed model.h
johapau Jul 27, 2023
a2867ab
reproduction number test added
johapau Jul 27, 2023
7851f20
Merge branch '677-computation-of-the-effective-reproduction-number-in…
johapau Jul 27, 2023
500e389
nothing major
johapau Jul 27, 2023
7792f5b
First calculations to find the reproduction number
johapau Aug 7, 2023
382843c
temporary save
johapau Aug 12, 2023
5da4e8f
stash
johapau Aug 14, 2023
d6718c5
stashed
johapau Aug 14, 2023
fec6ee5
stash
johapau Aug 15, 2023
1bea8dd
stash
johapau Aug 16, 2023
8ac1b6e
changes
johapau Aug 24, 2023
4d3767d
finished calculation
johapau Aug 24, 2023
f18362b
adjustment
johapau Aug 24, 2023
21ee673
documentation
johapau Aug 24, 2023
5036a2e
changes
johapau Aug 25, 2023
a7b0b80
Still have to correct errors in testing
johapau Aug 29, 2023
1f4d801
tests finished
johapau Aug 31, 2023
2e81e62
reversed changes to ode_secir
johapau Aug 31, 2023
d57f941
changes
johapau Aug 31, 2023
a61aa7a
formatting
johapau Aug 31, 2023
1e372aa
CI changes
johapau Aug 31, 2023
e2a36a7
test results calculated by hand
johapau Sep 7, 2023
f5dadb7
tests modified
johapau Sep 7, 2023
3d26c4d
output
johapau Sep 7, 2023
ea45b2c
output all
johapau Sep 7, 2023
b41708b
new tests
johapau Sep 7, 2023
09a7674
changes to indices
johapau Sep 8, 2023
9716861
Index check
johapau Sep 8, 2023
5a43fdf
changes
johapau Sep 8, 2023
f5993fb
Merge github.com:DLR-SC/memilio into 677-computation-of-the-effective…
johapau Sep 8, 2023
6f0aa91
integrated new compartments
johapau Sep 8, 2023
281eac3
removed comment
johapau Sep 8, 2023
125f7bb
remove iomanip
johapau Sep 8, 2023
37e4123
unnecessary includes
johapau Sep 8, 2023
eb860ce
tests added
johapau Sep 11, 2023
531dfdc
added one test
johapau Sep 28, 2023
0ff5c48
changes
johapau Sep 28, 2023
c7e863d
considered case of empty agegroups
johapau Sep 28, 2023
964135b
changes to total population
johapau Sep 29, 2023
2cfeeb9
total population zero handling modified
johapau Sep 29, 2023
0d66010
total population zero
johapau Sep 29, 2023
ba8c7c2
changes seasonality
johapau Sep 29, 2023
3b039a0
tests modified
johapau Oct 5, 2023
92215e8
additional test
johapau Oct 5, 2023
0c79824
better invertibility checking
johapau Oct 13, 2023
b0b451c
fixed CI error
johapau Oct 13, 2023
e33b287
work in progress
johapau Nov 6, 2023
200c9b7
changes undone, only time independent
johapau Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
261 changes: 261 additions & 0 deletions cpp/models/ode_secir/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@

#include "memilio/compartments/compartmentalmodel.h"
#include "memilio/compartments/simulation.h"
#include "memilio/config.h"
#include "memilio/epidemiology/populations.h"
#include "memilio/io/io.h"
#include "ode_secir/infection_state.h"
#include "memilio/math/interpolation.h"
#include "ode_secir/parameters.h"
#include "memilio/math/smoother.h"
#include "memilio/math/eigen_util.h"
Expand Down Expand Up @@ -308,6 +311,264 @@ double get_infections_relative(const Simulation<Base>& sim, double /*t*/, const
return inf_rel;
}

/**
*@brief Computes the reproduction number at a given index time of the Model output obtained by the Simulation.
*@param t_idx The index time at which the reproduction number is computed.
*@param sim The simulation handling the secir model
*@tparam Base simulation type that uses a secir compartment model. see Simulation.
*@returns The computed reproduction number at the provided index time.
*/

template <class Base>
IOResult<ScalarType> get_reproduction_number(size_t t_idx, const Simulation<Base>& sim)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know/assume that this contains the derivative of the ODE so a lot of code-doubling happens. Generally it is just not a very good a idea to introduce redudant code parts. We should maybe think of how only one responsible code part returns for instance the mechanism of X and then this is used for get_derivatives and get_reproduction_number.

{

if (!(t_idx < static_cast<size_t>(sim.get_result().get_num_time_points()))) {
return mio::failure(mio::StatusCode::OutOfRange, "t_idx is not a valid index for the TimeSeries");
}

auto const& params = sim.get_model().parameters;
const size_t num_groups = (size_t)sim.get_model().parameters.get_num_groups();
size_t num_infected_compartments = 5;
size_t total_infected_compartments = num_infected_compartments * num_groups;

Eigen::MatrixXd F(total_infected_compartments, total_infected_compartments);
Eigen::MatrixXd V(total_infected_compartments, total_infected_compartments);
F = Eigen::MatrixXd::Zero(total_infected_compartments,
total_infected_compartments); //Initialize matrices F and V with zeroes
V = Eigen::MatrixXd::Zero(total_infected_compartments, total_infected_compartments);

auto test_and_trace_required = 0.0;
auto icu_occupancy = 0.0;
for (auto i = AgeGroup(0); i < (mio::AgeGroup)num_groups; ++i) {
auto rateINS = 0.5 / (params.template get<IncubationTime>()[i] - params.template get<SerialInterval>()[i]);
test_and_trace_required +=
(1 - params.template get<RecoveredPerInfectedNoSymptoms>()[i]) * rateINS *
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedNoSymptoms})];
icu_occupancy += sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedCritical})];
}

double season_val =
(1 + params.template get<Seasonality>() *
sin(3.141592653589793 * (std::fmod((sim.get_model().parameters.template get<StartDay>() +
sim.get_result().get_time(t_idx)),
365.0) /
182.5 +
0.5)));
ContactMatrixGroup const& contact_matrix = sim.get_model().parameters.template get<ContactPatterns>();

Eigen::MatrixXd cont_freq_eff(num_groups, num_groups);
Eigen::MatrixXd riskFromInfectedSymptomatic_derivatives(num_groups, num_groups);
Eigen::VectorXd divN(num_groups);
Eigen::VectorXd riskFromInfectedSymptomatic(num_groups);
Eigen::VectorXd rateINS(num_groups);

for (mio::AgeGroup k = 0; k < (mio::AgeGroup)num_groups; k++) {
double temp = sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Susceptible})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Exposed})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedNoSymptoms})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSymptoms})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSevere})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedCritical})] +
sim.get_result().get_value(
t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Recovered})];
if (temp == 0) {
temp = 1;
johapau marked this conversation as resolved.
Show resolved Hide resolved
}
divN[(size_t)k] = 1 / temp;

riskFromInfectedSymptomatic[(size_t)k] = smoother_cosine(
test_and_trace_required, params.template get<TestAndTraceCapacity>(),
(params.template get<TestAndTraceCapacity>()) * 5, params.template get<RiskOfInfectionFromSymptomatic>()[k],
params.template get<MaxRiskOfInfectionFromSymptomatic>()[k]);

rateINS[(size_t)k] =
0.5 / (params.template get<IncubationTime>()[k] - params.template get<SerialInterval>()[(mio::AgeGroup)k]);

for (mio::AgeGroup l = 0; l < (mio::AgeGroup)num_groups; l++) {
if (test_and_trace_required < params.template get<TestAndTraceCapacity>() ||
test_and_trace_required > 5 * params.template get<TestAndTraceCapacity>()) {
riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) = 0;
}
else {
riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) =
-0.5 * 3.14159265358979323846 *
(params.template get<MaxRiskOfInfectionFromSymptomatic>()[k] -
params.template get<RiskOfInfectionFromSymptomatic>()[k]) /
(4 * params.template get<TestAndTraceCapacity>()) *
(1 - params.template get<RecoveredPerInfectedNoSymptoms>()[l]) * rateINS[(size_t)l] *
std::sin(3.14159265358979323846 / (4 * params.template get<TestAndTraceCapacity>()) *
(test_and_trace_required - params.template get<TestAndTraceCapacity>()));
}
}

for (Eigen::Index l = 0; l < (Eigen::Index)num_groups; l++) {
cont_freq_eff(l, (size_t)k) =
season_val * contact_matrix.get_matrix_at(static_cast<double>(t_idx))(
static_cast<Eigen::Index>((size_t)l), static_cast<Eigen::Index>((size_t)k));
}
}

//Check criterion if matrix V will be invertible by checking if subblock J is invertible
Eigen::MatrixXd J(num_groups, num_groups);
J = Eigen::MatrixXd::Zero(num_groups, num_groups);
for (size_t i = 0; i < num_groups; i++) {
J(i, i) = 1 / (params.template get<TimeInfectedCritical>()[(mio::AgeGroup)i]);

if (!(icu_occupancy < 0.9 * params.template get<ICUCapacity>() ||
icu_occupancy > (double)(params.template get<ICUCapacity>()))) {
for (size_t j = 0; j < num_groups; j++) {
J(i, j) -= sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
{(mio::AgeGroup)i, InfectionState::InfectedSevere})] /
params.template get<TimeInfectedSevere>()[(mio::AgeGroup)i] * 5 *
params.template get<CriticalPerSevere>()[(mio::AgeGroup)i] * 3.141592653589793 /
(params.template get<ICUCapacity>()) *
std::sin(3.141592653589793 / (0.1 * params.template get<ICUCapacity>()) *
(icu_occupancy - 0.9 * params.template get<ICUCapacity>()));
}
}
}

//Try to invert J
Eigen::FullPivLU<Eigen::MatrixXd> lu(J); //Check invertibility via LU Decomposition
if (!lu.isInvertible()) {
return mio::failure(mio::StatusCode::UnknownError, "Matrix V is not invertible");
}

//Eigen::Array<ScalarType, 3, 3> Jnew = J;

//Initialize the matrix F
for (size_t i = 0; i < num_groups; i++) {

for (size_t j = 0; j < num_groups; j++) {

double temp = 0;
for (Eigen::Index k = 0; k < (Eigen::Index)num_groups; k++) {
temp += cont_freq_eff(i, k) *
sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
{(mio::AgeGroup)k, InfectionState::InfectedSymptoms})] *
riskFromInfectedSymptomatic_derivatives(k, j) * divN[k];
}

F(i, j + num_groups) =
sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
{(mio::AgeGroup)i, InfectionState::Susceptible})] *
params.template get<TransmissionProbabilityOnContact>()[(mio::AgeGroup)i] *
(cont_freq_eff(i, j) * params.template get<RelativeTransmissionNoSymptoms>()[(mio::AgeGroup)j] *
divN[(size_t)j] +
temp);
}

for (size_t j = 0; j < num_groups; j++) {
F(i, j + 2 * num_groups) = sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
{(mio::AgeGroup)i, InfectionState::Susceptible})] *
params.template get<TransmissionProbabilityOnContact>()[(mio::AgeGroup)i] *
cont_freq_eff(i, j) * riskFromInfectedSymptomatic[(size_t)j] * divN[(size_t)j];
}
}

//Initialize the matrix V
for (Eigen::Index i = 0; i < (Eigen::Index)num_groups; i++) {

double rateE = 1.0 / (2 * params.template get<SerialInterval>()[(mio::AgeGroup)i] -
params.template get<IncubationTime>()[(mio::AgeGroup)i]);

double criticalPerSevereAdjusted = smoother_cosine(
icu_occupancy, 0.90 * params.template get<ICUCapacity>(), params.template get<ICUCapacity>(),
params.template get<CriticalPerSevere>()[(mio::AgeGroup)i], 0);

V(i, i) = rateE;
V(i + num_groups, i) = -rateE;
V(i + num_groups, i + num_groups) = rateINS[i];
V(i + 2 * num_groups, i + num_groups) =
-(1 - params.template get<RecoveredPerInfectedNoSymptoms>()[(mio::AgeGroup)i]) * rateINS[i];
V(i + 2 * num_groups, i + 2 * num_groups) = (1 / params.template get<TimeInfectedSymptoms>()[(mio::AgeGroup)i]);
V(i + 3 * num_groups, i + 2 * num_groups) =
-params.template get<SeverePerInfectedSymptoms>()[(mio::AgeGroup)i] /
params.template get<TimeInfectedSymptoms>()[(mio::AgeGroup)i];
V(i + 3 * num_groups, i + 3 * num_groups) = 1 / (params.template get<TimeInfectedSevere>()[(mio::AgeGroup)i]);
V(i + 4 * num_groups, i + 3 * num_groups) =
-criticalPerSevereAdjusted / (params.template get<TimeInfectedSevere>()[(mio::AgeGroup)i]);

for (size_t j = 0; j < num_groups; j++) {
V(i + 4 * num_groups, j + 4 * num_groups) = J(i, j);
}
}

V = V.inverse();

//Compute F*V
Eigen::MatrixXd NextGenMatrix(5 * num_groups, 5 * num_groups);
NextGenMatrix = F * V;

//Compute the largest eigenvalue in absolute value
Eigen::ComplexEigenSolver<Eigen::MatrixXd> ces;

ces.compute(NextGenMatrix);
const Eigen::VectorXcd eigen_vals = ces.eigenvalues();

Eigen::VectorXd eigen_vals_abs;
eigen_vals_abs.resize(eigen_vals.size());

for (int i = 0; i < eigen_vals.size(); i++) {
eigen_vals_abs[i] = std::abs(eigen_vals[i]);
}
return mio::success(eigen_vals_abs.maxCoeff());
}
/**
*@brief Computes the reproduction number for all time points of the Model output obtained by the Simulation.
*@param sim The Model Simulation.
*@returns vector containing all reproduction numbers
*/

template <class Base>
Eigen::VectorXd get_reproduction_numbers(const Simulation<Base>& sim)
{
Eigen::VectorXd temp(sim.get_result().get_num_time_points());
for (int i = 0; i < sim.get_result().get_num_time_points(); i++) {
temp[i] = get_reproduction_number((size_t)i, sim).value();
}
return temp;
}

/**
*@brief Computes the reproduction number at a given time point of the Model output obtained by the Simulation. If the particular time point is not inside the output, a linearly interpolated value is returned.
*@param t_value The time point at which the reproduction number is computed.
*@param sim The Model Simulation.
*@returns The computed reproduction number at the provided time point, potentially using linear interpolation.
*/
template <class Base>
IOResult<ScalarType> get_reproduction_number(ScalarType t_value, const Simulation<Base>& sim)
{
if (t_value < sim.get_result().get_time(0) || t_value > sim.get_result().get_last_time()) {
return mio::failure(mio::StatusCode::OutOfRange,
"Cannot interpolate reproduction number outside computed horizon of the TimeSeries");
}

if (t_value == sim.get_result().get_time(0)) {
return mio::success(get_reproduction_number((size_t)0, sim).value());
}

auto times = std::vector<ScalarType>(sim.get_result().get_times().begin(), sim.get_result().get_times().end());

auto time_late = std::distance(times.begin(), std::lower_bound(times.begin(), times.end(), t_value));

ScalarType y1 = get_reproduction_number(static_cast<size_t>(time_late - 1), sim).value();
ScalarType y2 = get_reproduction_number(static_cast<size_t>(time_late), sim).value();

auto result = linear_interpolation(t_value, sim.get_result().get_time(time_late - 1),
sim.get_result().get_time(time_late), y1, y2);
return mio::success(static_cast<ScalarType>(result));
}

/**
* Get migration factors.
* Used by migration graph simulation.
Expand Down
Loading
Loading