From 6dba31c448c1058888da685f58cb4944aefdac0d Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 11 Jul 2024 03:33:58 +0200 Subject: [PATCH 01/26] implement detailled mobility --- cpp/examples/CMakeLists.txt | 4 + cpp/examples/ode_secir_detailed_mobility.cpp | 104 +++ cpp/memilio/CMakeLists.txt | 2 + cpp/memilio/compartments/simulation.h | 9 + cpp/memilio/io/mobility_io.cpp | 89 +++ cpp/memilio/io/mobility_io.h | 22 +- cpp/memilio/mobility/graph_simulation.h | 488 +++++++++++- .../metapopulation_mobility_detailed.cpp | 25 + .../metapopulation_mobility_detailed.h | 719 ++++++++++++++++++ .../metapopulation_mobility_instant.h | 2 +- 10 files changed, 1459 insertions(+), 5 deletions(-) create mode 100644 cpp/examples/ode_secir_detailed_mobility.cpp create mode 100644 cpp/memilio/mobility/metapopulation_mobility_detailed.cpp create mode 100644 cpp/memilio/mobility/metapopulation_mobility_detailed.h diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 0d3b703ffe..ec5aa633b8 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -72,6 +72,10 @@ add_executable(ode_secirvvs_example ode_secirvvs.cpp) target_link_libraries(ode_secirvvs_example PRIVATE memilio ode_secirvvs) target_compile_options(ode_secirvvs_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(ode_secir_detailed_mobility ode_secir_detailed_mobility.cpp) +target_link_libraries(ode_secir_detailed_mobility PRIVATE memilio ode_secir) +target_compile_options(ode_secir_detailed_mobility PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + add_executable(ode_secir_ageres_example ode_secir_ageres.cpp) target_link_libraries(ode_secir_ageres_example PRIVATE memilio ode_secir) target_compile_options(ode_secir_ageres_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/examples/ode_secir_detailed_mobility.cpp b/cpp/examples/ode_secir_detailed_mobility.cpp new file mode 100644 index 0000000000..0e378992b8 --- /dev/null +++ b/cpp/examples/ode_secir_detailed_mobility.cpp @@ -0,0 +1,104 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "memilio/mobility/metapopulation_mobility_detailed.h" +#include "memilio/io/mobility_io.h" +#include "memilio/compartments/parameter_studies.h" +#include "ode_secir/parameter_space.h" +#include "ode_secir/parameters_io.h" +#include + +int main() +{ + const auto t0 = 0.; + const auto dt = 0.5; + const auto tmax = 10.; + + ScalarType cont_freq = 10; // see Polymod study + + ScalarType nb_total_t0 = 10000, nb_exp_t0 = 100, nb_inf_t0 = 50, nb_car_t0 = 50, nb_hosp_t0 = 20, nb_icu_t0 = 10, + nb_rec_t0 = 10, nb_dead_t0 = 0; + + mio::osecir::Model model(1); + mio::AgeGroup nb_groups = model.parameters.get_num_groups(); + ScalarType fact = 1.0 / (ScalarType)(size_t)nb_groups; + + auto& params = model.parameters; + + params.set>(std::numeric_limits::max()); + params.set(0); + params.set>(0); + + for (auto i = mio::AgeGroup(0); i < nb_groups; i++) { + params.get>()[i] = 3.2; + params.get>()[i] = 2.0; + params.get>()[i] = 6.; + params.get>()[i] = 12; + params.get>()[i] = 8; + + model.populations[{i, mio::osecir::InfectionState::Exposed}] = fact * nb_exp_t0; + model.populations[{i, mio::osecir::InfectionState::InfectedNoSymptoms}] = fact * nb_car_t0; + model.populations[{i, mio::osecir::InfectionState::InfectedNoSymptomsConfirmed}] = 0; + model.populations[{i, mio::osecir::InfectionState::InfectedSymptoms}] = fact * nb_inf_t0; + model.populations[{i, mio::osecir::InfectionState::InfectedSymptomsConfirmed}] = 0; + model.populations[{i, mio::osecir::InfectionState::InfectedSevere}] = fact * nb_hosp_t0; + model.populations[{i, mio::osecir::InfectionState::InfectedCritical}] = fact * nb_icu_t0; + model.populations[{i, mio::osecir::InfectionState::Recovered}] = fact * nb_rec_t0; + model.populations[{i, mio::osecir::InfectionState::Dead}] = fact * nb_dead_t0; + model.populations.set_difference_from_group_total({i, mio::osecir::InfectionState::Susceptible}, + fact * nb_total_t0); + + params.get>()[i] = 0.05; + params.get>()[i] = 0.67; + params.get>()[i] = 0.09; + params.get>()[i] = 0.25; + params.get>()[i] = 0.2; + params.get>()[i] = 0.25; + params.get>()[i] = 0.3; + } + + params.apply_constraints(); + + mio::ContactMatrixGroup& contact_matrix = params.get>(); + contact_matrix[0] = + mio::ContactMatrix(Eigen::MatrixXd::Constant((size_t)nb_groups, (size_t)nb_groups, fact * cont_freq)); + + mio::GraphDetailed>>, + mio::MigrationEdgeDetailed> + g; + for (size_t county_id = 0; county_id < 3; county_id++) { + g.add_node(county_id, model, 0.0); + } + + // Graph is always complete here + for (size_t county_idx_i = 0; county_idx_i < g.nodes().size(); ++county_idx_i) { + for (size_t county_idx_j = 0; county_idx_j < g.nodes().size(); ++county_idx_j) { + if (county_idx_i == county_idx_j) + continue; + g.add_edge(county_idx_i, county_idx_j, + Eigen::VectorXd::Constant( + (size_t)mio::osecir::InfectionState::Count * static_cast(nb_groups), 0.01)); + } + } + + auto sim = mio::make_migration_sim(t0, dt, std::move(g)); + sim.advance(tmax); + + return 0; +} diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index fd24f11e8e..8495c2260b 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -60,6 +60,8 @@ add_library(memilio mobility/metapopulation_mobility_instant.cpp mobility/metapopulation_mobility_stochastic.h mobility/metapopulation_mobility_stochastic.cpp + mobility/metapopulation_mobility_detailed.cpp + mobility/metapopulation_mobility_detailed.h mobility/graph_simulation.h mobility/graph_simulation.cpp mobility/graph.h diff --git a/cpp/memilio/compartments/simulation.h b/cpp/memilio/compartments/simulation.h index 8b3307f87f..4a9f8d7892 100644 --- a/cpp/memilio/compartments/simulation.h +++ b/cpp/memilio/compartments/simulation.h @@ -60,6 +60,15 @@ class Simulation { } + Simulation(const Simulation& other) + : m_integratorCore(other.m_integratorCore) + , m_model(std::make_unique(*other.m_model)) + , m_integrator(m_integratorCore) + , m_result(other.m_result) + , m_dt(other.m_dt) + { + } + /** * @brief Set the integrator core used in the simulation. * @param[in] integrator A shared pointer to an object derived from IntegratorCore. diff --git a/cpp/memilio/io/mobility_io.cpp b/cpp/memilio/io/mobility_io.cpp index da7f3caf1e..2dc1fbdd9e 100644 --- a/cpp/memilio/io/mobility_io.cpp +++ b/cpp/memilio/io/mobility_io.cpp @@ -135,4 +135,93 @@ IOResult read_mobility_plain(const std::string& filename) return success(migration); } +IOResult read_duration_stay(const std::string& filename) +{ + BOOST_OUTCOME_TRY(auto&& num_lines, count_lines(filename)); + + if (num_lines == 0) { + return success(Eigen::MatrixXd(0, 0)); + } + + std::fstream file; + file.open(filename, std::ios::in); + if (!file.is_open()) { + return failure(StatusCode::FileNotFound, filename); + } + + Eigen::VectorXd duration(num_lines); + + try { + std::string tp; + int linenumber = 0; + while (getline(file, tp)) { + auto line = split(tp, ' '); + Eigen::Index i = static_cast(linenumber); + duration(i) = std::stod(line[0]); + linenumber++; + } + } + catch (std::runtime_error& ex) { + return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); + } + + return success(duration); +} + +IOResult>>> read_path_mobility(const std::string& filename) +{ + BOOST_OUTCOME_TRY(auto&& num_lines, count_lines(filename)); + + if (num_lines == 0) { + std::vector>> arr(0, std::vector>(0)); + return success(arr); + } + + std::fstream file; + file.open(filename, std::ios::in); + if (!file.is_open()) { + return failure(StatusCode::FileNotFound, filename); + } + + const int num_nodes = static_cast(std::sqrt(num_lines)); + std::vector>> arr(num_nodes, std::vector>(num_nodes)); + + try { + std::string tp; + while (getline(file, tp)) { + auto line = split(tp, ' '); + auto indx_x = std::stoi(line[0]); + auto indx_y = std::stoi(line[1]); + if (indx_x != indx_y) { + auto path = std::accumulate(line.begin() + 2, line.end(), std::string("")); + + // string -> vector of integers + std::vector path_vec; + + // Remove the square brackets and \r + path = path.substr(1, path.size() - 3); + std::stringstream ss(path); + std::string token; + + // get numbers and save them in path_vec + while (std::getline(ss, token, ',')) { + path_vec.push_back(std::stoi(token)); + } + + for (int number : path_vec) { + arr[indx_x][indx_y].push_back(number); + } + } + else { + arr[indx_x][indx_y].push_back(static_cast(indx_x)); + } + } + } + catch (std::runtime_error& ex) { + return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); + } + + return success(arr); +} + } // namespace mio diff --git a/cpp/memilio/io/mobility_io.h b/cpp/memilio/io/mobility_io.h index c30f923d4c..d1f5bef9a7 100644 --- a/cpp/memilio/io/mobility_io.h +++ b/cpp/memilio/io/mobility_io.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef READ_TWITTER_H -#define READ_TWITTER_H +#ifndef MIO_MOBILITY_IO_H +#define MIO_MOBILITY_IO_H #include "memilio/io/json_serializer.h" #include "memilio/mobility/graph.h" @@ -57,6 +57,22 @@ IOResult read_mobility_formatted(const std::string& filename); */ IOResult read_mobility_plain(const std::string& filename); +/** + * @brief Reads txt file containing the duration of stay in each county. + Writes it into a Eigen vector of size N, where N is the number of local entites. + * @param filename name of file to be read + * @return IOResult containing the duration of stay in each local entity + */ +IOResult read_duration_stay(const std::string& filename); + +/** + * @brief For each edge we have the path from the start node to the end node. This functions reads the file and returns the path for each edge. + * + * @param filename Filename of the file containing the paths. + * @return IOResult>>> contains the paths for each edge. + */ +IOResult>>> read_path_mobility(const std::string& filename); + #ifdef MEMILIO_HAS_JSONCPP /** @@ -188,4 +204,4 @@ IOResult>> read_graph(const std::string& di } // namespace mio -#endif // READ_TWITTER_H +#endif // MIO_MOBILITY_IO_H diff --git a/cpp/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index 003ae08793..dbe71ea083 100644 --- a/cpp/memilio/mobility/graph_simulation.h +++ b/cpp/memilio/mobility/graph_simulation.h @@ -20,8 +20,10 @@ #ifndef MIO_MOBILITY_GRAPH_SIMULATION_H #define MIO_MOBILITY_GRAPH_SIMULATION_H +#include "memilio/math/euler.h" #include "memilio/mobility/graph.h" #include "memilio/utils/random_number_generator.h" +#include namespace mio { @@ -247,7 +249,484 @@ class GraphSimulationStochastic RandomNumberGenerator m_rng; }; -template +template > +class GraphSimulationDetailed +{ + +public: + using edge_function = edge_f; + using node_function = std::function; + + GraphSimulationDetailed(double t0, double dt, const GraphDetailed& g, const node_function& node_func, + const edge_function&& edge_func) + : m_dt(dt) + , m_t(double(t0)) + , m_graph(g) + , m_node_func(node_func) + , m_edge_func(edge_func) + { + } + + GraphSimulationDetailed(double t0, double dt, GraphDetailed&& g, const node_function& node_func, + const edge_function&& edge_func) + : m_dt(dt) + , m_t(double(t0)) + , m_graph(std::move(g)) + , m_node_func(node_func) + , m_edge_func(edge_func) + { + } + + inline double round_second_decimal(double x) + { + return std::round(x * 100.0) / 100.0; + } + + double get_t() const + { + return m_t; + } + + GraphDetailed& get_graph() & + { + return m_graph; + } + + const GraphDetailed& get_graph() const& + { + return m_graph; + } + + GraphDetailed&& get_graph() && + { + return std::move(m_graph); + } + + void advance(double t_max = 1.0) + { + ScalarType dt_first_mobility = + std::accumulate(m_graph.edges().begin(), m_graph.edges().end(), std::numeric_limits::max(), + [&](ScalarType current_min, const auto& e) { + auto traveltime_per_region = round_second_decimal(e.traveltime / e.path.size()); + if (traveltime_per_region < 0.01) + traveltime_per_region = 0.01; + auto start_mobility = + round_second_decimal(1 - 2 * (traveltime_per_region * e.path.size()) - + m_graph.nodes()[e.end_node_idx].stay_duration); + if (start_mobility < 0) { + start_mobility = 0.; + } + return std::min(current_min, start_mobility); + }); + + // set population to zero in mobility nodes before starting + for (auto& n : m_graph.nodes()) { + n.mobility.get_result().get_last_value().setZero(); + } + + auto min_dt = 0.01; + double t_begin = m_t - 1.; + + // calc schedule for each edge + precompute_schedule(); + while (m_t - epsilon < t_max) { + // auto start = std::chrono::system_clock::now(); + + t_begin += 1; + if (m_t + dt_first_mobility > t_max) { + dt_first_mobility = t_max - m_t; + for (auto& n : m_graph.nodes()) { + m_node_func(m_t, dt_first_mobility, n.property); + } + break; + } + + for (auto& node : m_graph.nodes()) { + node.mobility.get_simulation().set_integrator(std::make_shared>()); + } + + size_t indx_schedule = 0; + while (t_begin + 1 > m_t + 1e-10) { + advance_edges(indx_schedule); + + // first we integrate the nodes in time. Afterwards the update on the edges is done. + // We start with the edges since the values for t0 are given. + advance_local_nodes(indx_schedule); + advance_mobility_nodes(indx_schedule); + + indx_schedule++; + m_t += min_dt; + } + // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate + // the state of the indivuals moving between different counties. + // Therefore there can be differences with the states given by the integrator used for the mobility node. + for (auto& n : m_graph.nodes()) { + n.mobility.get_result().get_last_value().setZero(); + } + } + } + +private: + GraphDetailed m_graph; + double m_t; + double m_dt; + node_function m_node_func; + edge_function m_edge_func; + + // describes the schedule for each edge, i.e. which node is visited at which time step + std::vector> schedule_edges; + + // defines if people from a edge are currently in a mobility node. This is necessary to determine the correct + // position in the schedule. Otherwise we could add a specific identifier in schedule_edges. + std::vector> mobility_schedule_edges; + + // describes the syncronization steps which are necessary for the integrator in the edges and nodes. + std::vector> mb_int_schedule; + std::vector> ln_int_schedule; + + // defines the edges on which mobility takes place for each time step + std::vector> edges_mobility; + + // same for the nodes + std::vector> nodes_mobility; + std::vector> nodes_mobility_m; + + // first mobility activites per edge + std::vector first_mobility; + + const double epsilon = std::numeric_limits::epsilon(); + + void precompute_schedule() + { + // print transi + const size_t timesteps = 100; + schedule_edges.reserve(m_graph.edges().size()); + mobility_schedule_edges.reserve(m_graph.edges().size()); + + for (auto& e : m_graph.edges()) { + // 100 since we round to second decimal + std::vector schedule(timesteps, 0.); + std::vector is_mobility_node(timesteps, false); + + double traveltime_per_region = round_second_decimal(e.traveltime / e.path.size()); + + if (traveltime_per_region < 0.01) + traveltime_per_region = 0.01; + + double start_mobility = round_second_decimal(0 + 1 - 2 * (traveltime_per_region * e.path.size()) - + m_graph.nodes()[e.end_node_idx].stay_duration); + if (start_mobility < 0.0) { + start_mobility = 0.; + } + + double arrive_at = start_mobility + traveltime_per_region * e.path.size(); + + std::fill(schedule.begin(), schedule.begin() + static_cast((start_mobility + epsilon) * 100), + e.start_node_idx); + + // all values true between start mob und arrive + std::fill(is_mobility_node.begin() + static_cast((start_mobility + epsilon) * 100), + is_mobility_node.begin() + static_cast((arrive_at + epsilon) * 100), true); + int count_true = std::count(is_mobility_node.begin(), is_mobility_node.end(), true); + size_t current_index = static_cast((start_mobility + epsilon) * 100); + for (size_t county : e.path) { + std::fill(schedule.begin() + current_index, + schedule.begin() + current_index + + static_cast((traveltime_per_region + epsilon) * 100), + county); + current_index += static_cast((traveltime_per_region + epsilon) * 100); + } + + // stay in destination county + std::fill(schedule.begin() + current_index, schedule.end() - count_true, e.path[e.path.size() - 1]); + + // revert trip for return. + std::fill(is_mobility_node.end() - count_true, is_mobility_node.end(), true); + + auto first_true = std::find(is_mobility_node.begin(), is_mobility_node.end(), true); + auto last_true = std::find(is_mobility_node.rbegin(), is_mobility_node.rend(), true); + + // If all values are false, we get an error + if (first_true != is_mobility_node.end() && last_true != is_mobility_node.rend()) { + int first_index = std::distance(is_mobility_node.begin(), first_true); + std::vector path_reversed(schedule.begin() + first_index, + schedule.begin() + first_index + count_true); + std::reverse(path_reversed.begin(), path_reversed.end()); + std::copy(path_reversed.begin(), path_reversed.end(), schedule.end() - count_true); + schedule_edges.push_back(schedule); + mobility_schedule_edges.push_back(is_mobility_node); + } + else { + log_error("Error in creating schedule."); + } + } + + // iterate over nodes + size_t indx_node = 0; + for (auto& n : m_graph.nodes()) { + // local node and automatical add zero, since we want to start at t=0 and start with integrating all nodes to the next dt + std::vector order_local_node = {0}; + std::vector indx_edges; + for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { + if (schedule_edges[indx_edge][0] == indx_node && !mobility_schedule_edges[indx_edge][0]) { + indx_edges.push_back(indx_edge); + } + } + + for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { + std::vector indx_edges_new; + for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { + if (schedule_edges[indx_edge][indx_t] == indx_node && !mobility_schedule_edges[indx_edge][indx_t]) { + indx_edges_new.push_back(indx_edge); + } + } + + if (indx_edges_new.size() != indx_edges.size() || + !std::equal(indx_edges.begin(), indx_edges.end(), indx_edges_new.begin())) { + order_local_node.push_back(indx_t); + indx_edges = indx_edges_new; + } + } + if (order_local_node[order_local_node.size() - 1] != timesteps - 1) + order_local_node.push_back(timesteps - 1); + ln_int_schedule.push_back(order_local_node); + + // mobility node + std::vector order_mobility_node; + std::vector indx_edges_mobility; + for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { + if (schedule_edges[indx_edge][0] == indx_node && mobility_schedule_edges[indx_edge][0]) { + indx_edges_mobility.push_back(indx_edge); + } + } + for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { + std::vector indx_edges_mobility_new; + for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { + if (schedule_edges[indx_edge][indx_t] == indx_node && mobility_schedule_edges[indx_edge][indx_t]) { + indx_edges_mobility_new.push_back(indx_edge); + } + } + + if (indx_edges_mobility_new.size() != indx_edges_mobility.size() || + !std::equal(indx_edges_mobility.begin(), indx_edges_mobility.end(), + indx_edges_mobility_new.begin())) { + order_mobility_node.push_back(indx_t); + indx_edges_mobility = indx_edges_mobility_new; + } + } + mb_int_schedule.push_back(order_mobility_node); + indx_node++; + } + + auto indx_edge = 0; + first_mobility.reserve(m_graph.edges().size()); + for (auto& e : m_graph.edges()) { + this->first_mobility[indx_edge] = std::distance( + mobility_schedule_edges[indx_edge].begin(), + std::find(mobility_schedule_edges[indx_edge].begin(), mobility_schedule_edges[indx_edge].end(), true)); + indx_edge++; + } + + edges_mobility.reserve(timesteps); + nodes_mobility.reserve(timesteps); + nodes_mobility_m.reserve(timesteps); + + // we handle indx_current = 0 separately since we want have added them always in the + // intregration schedule so this would lead to wrong results + // here we iterate over first mobility activities and add the edges indx when there is a start at t = 0 + std::vector temp_edges_mobility; + indx_edge = 0; + for (auto& start_time : first_mobility) { + if (start_time == 0) { + temp_edges_mobility.push_back(indx_edge); + } + indx_edge++; + } + edges_mobility.push_back(std::move(temp_edges_mobility)); + + // same for nodes + std::vector temp_nodes_mobility(m_graph.nodes().size()); + std::iota(temp_nodes_mobility.begin(), temp_nodes_mobility.end(), 0); + nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); + + std::vector temp_nodes_mobility_m; + size_t node_indx = 0; + for (auto& n : m_graph.nodes()) { + if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), 0)) { + temp_nodes_mobility_m.push_back(node_indx); + node_indx++; + } + } + nodes_mobility_m.push_back(temp_nodes_mobility_m); + + for (size_t indx_current = 1; indx_current < timesteps; ++indx_current) { + std::vector temp_edge_mobility; + indx_edge = 0; + for (auto& e : m_graph.edges()) { + auto current_node_indx = schedule_edges[indx_edge][indx_current]; + if (indx_current >= first_mobility[indx_edge]) { + if (mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(mb_int_schedule[current_node_indx].begin(), + mb_int_schedule[current_node_indx].end(), indx_current)) + temp_edge_mobility.push_back(indx_edge); + else if (!mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(ln_int_schedule[current_node_indx].begin(), + ln_int_schedule[current_node_indx].end(), indx_current)) + temp_edge_mobility.push_back(indx_edge); + } + indx_edge++; + } + edges_mobility.push_back(temp_edge_mobility); + + // reset temp_nodes_mobility + temp_nodes_mobility.clear(); + temp_nodes_mobility_m.clear(); + node_indx = 0; + for (auto& n : m_graph.nodes()) { + if (std::binary_search(ln_int_schedule[node_indx].begin(), ln_int_schedule[node_indx].end(), + indx_current)) { + temp_nodes_mobility.push_back(node_indx); + } + + if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), + indx_current)) { + temp_nodes_mobility_m.push_back(node_indx); + } + node_indx++; + } + nodes_mobility.push_back(temp_nodes_mobility); + nodes_mobility_m.push_back(temp_nodes_mobility_m); + } + } + + void advance_edges(size_t indx_schedule) + { + for (const auto& edge_indx : edges_mobility[indx_schedule]) { + auto& e = m_graph.edges()[edge_indx]; + // first mobility activity + if (indx_schedule == first_mobility[edge_indx]) { + auto& node_from = m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; + auto& node_to = m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility; + // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); + } + // next mobility activity + else if (indx_schedule > first_mobility[edge_indx]) { + auto current_node_indx = schedule_edges[edge_indx][indx_schedule]; + bool in_mobility_node = mobility_schedule_edges[edge_indx][indx_schedule]; + + // determine dt, which is equal to the last integration/syncronization point in the current node + auto integrator_schedule_row = ln_int_schedule[current_node_indx]; + if (in_mobility_node) + integrator_schedule_row = mb_int_schedule[current_node_indx]; + // search the index of indx_schedule in the integrator schedule + const size_t indx_current = std::distance( + integrator_schedule_row.begin(), + std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); + + if (integrator_schedule_row[indx_current] != indx_schedule) + std::cout << "Error in schedule." + << "\n"; + + ScalarType dt_mobility; + if (indx_current == 0) { + dt_mobility = round_second_decimal(e.traveltime / e.path.size()); + if (dt_mobility < 0.01) + dt_mobility = 0.01; + } + else { + dt_mobility = + round_second_decimal((static_cast(integrator_schedule_row[indx_current]) - + static_cast(integrator_schedule_row[indx_current - 1])) / + 100 + + epsilon); + } + + // We have two cases. Either, we want to send the individuals to the next node, or we just want + // to update their state since a syncronization step is necessary in the current node. + if ((schedule_edges[edge_indx][indx_schedule] != schedule_edges[edge_indx][indx_schedule - 1]) || + (mobility_schedule_edges[edge_indx][indx_schedule] != + mobility_schedule_edges[edge_indx][indx_schedule - 1])) { + auto& node_from = mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].mobility + : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; + + auto& node_to = mobility_schedule_edges[edge_indx][indx_schedule] + ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility + : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property; + + if (indx_schedule < mobility_schedule_edges[edge_indx].size() - 1) { + // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); + } + // else + // the last time step is handled differently since we have to close the timeseries + // m_edge_func(m_t, dt_mobility, e.property, node_from, + // m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property, 2); + } + else { + auto& node_from = mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].mobility + : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; + + auto& node_to = mobility_schedule_edges[edge_indx][indx_schedule] + ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility + : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property; + + assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); + // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); + } + } + } + } + + void advance_local_nodes(size_t indx_schedule) + { + for (const auto& n_indx : nodes_mobility[indx_schedule]) { + auto& n = m_graph.nodes()[n_indx]; + const size_t indx_current = std::distance( + ln_int_schedule[n_indx].begin(), + std::lower_bound(ln_int_schedule[n_indx].begin(), ln_int_schedule[n_indx].end(), indx_schedule)); + + const size_t val_next = + (indx_current == ln_int_schedule[n_indx].size() - 1) ? 100 : ln_int_schedule[n_indx][indx_current + 1]; + const ScalarType next_dt = + round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + m_node_func(m_t, next_dt, n.property); + } + } + + void advance_mobility_nodes(size_t indx_schedule) + { + for (const size_t& n_indx : nodes_mobility_m[indx_schedule]) { + auto& n = m_graph.nodes()[n_indx]; + const size_t indx_current = std::distance( + mb_int_schedule[n_indx].begin(), + std::lower_bound(mb_int_schedule[n_indx].begin(), mb_int_schedule[n_indx].end(), indx_schedule)); + const size_t val_next = + (indx_current == mb_int_schedule[n_indx].size() - 1) ? 100 : mb_int_schedule[n_indx][indx_current + 1]; + const ScalarType next_dt = + round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + + // get all time points from the last integration step + auto& last_time_point = n.mobility.get_result().get_time(n.mobility.get_result().get_num_time_points() - 1); + // wenn last_time_point nicht innerhalb eines intervalls von 1-e10 von t liegt, dann setzte den letzten Zeitpunkt auf m_t + if (std::fabs(last_time_point - m_t) > 1e-10) { + n.mobility.get_result().get_last_time() = m_t; + } + m_node_func(m_t, next_dt, n.mobility); + Eigen::Index indx_min; + while (n.mobility.get_result().get_last_value().minCoeff(&indx_min) < -1e-7) { + Eigen::Index indx_max; + n.mobility.get_result().get_last_value().maxCoeff(&indx_max); + n.mobility.get_result().get_last_value()[indx_max] -= + n.mobility.get_result().get_last_value()[indx_min]; + n.mobility.get_result().get_last_value()[indx_min] = 0; + } + } + } +}; + auto make_graph_sim(FP t0, FP dt, Graph&& g, NodeF&& node_func, EdgeF&& edge_func) { return GraphSimulation>(t0, dt, std::forward(g), std::forward(node_func), @@ -261,5 +740,12 @@ auto make_graph_sim_stochastic(FP t0, FP dt, Graph&& g, NodeF&& node_func, EdgeF t0, dt, std::forward(g), std::forward(node_func), std::forward(edge_func)); } +template +auto make_graph_sim_detailed(double t0, double dt, GraphDetailed&& g, NodeF&& node_func, EdgeF&& edge_func) +{ + return GraphSimulationDetailed>( + t0, dt, std::forward(g), std::forward(node_func), std::forward(edge_func)); +} + } // namespace mio #endif //MIO_MOBILITY_GRAPH_SIMULATION_H diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.cpp b/cpp/memilio/mobility/metapopulation_mobility_detailed.cpp new file mode 100644 index 0000000000..cb50b08115 --- /dev/null +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.cpp @@ -0,0 +1,25 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "memilio/mobility/metapopulation_mobility_detailed.h" + +namespace mio +{ + +} // namespace mio \ No newline at end of file diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h new file mode 100644 index 0000000000..3751de92d6 --- /dev/null +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -0,0 +1,719 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef METAPOPULATION_MOBILITY_DETAILED_H +#define METAPOPULATION_MOBILITY_DETAILED_H + +#include "memilio/compartments/parameter_studies.h" +#include "memilio/epidemiology/simulation_day.h" +#include "memilio/mobility/graph_simulation.h" +#include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/utils/time_series.h" +#include "memilio/math/eigen.h" +#include "memilio/math/eigen_util.h" +#include "memilio/utils/metaprogramming.h" +#include "memilio/utils/compiler_diagnostics.h" +#include "memilio/math/euler.h" +#include "memilio/epidemiology/contact_matrix.h" +#include "memilio/epidemiology/dynamic_npis.h" +#include "memilio/compartments/simulation.h" +#include "memilio/utils/date.h" +#include "memilio/mobility/graph.h" +#include "memilio/io/mobility_io.h" + +#include "boost/filesystem.hpp" + +#include +#include + +namespace mio +{ + +template +struct NodeDetailed : Node { + template + NodeDetailed(int node_id, Args&&... args) + : Node(node_id, std::forward(args)...) + , stay_duration(0.5) + , mobility(std::forward(args)...) + { + } + + template + NodeDetailed(int node_id, double duration, Model property_arg, Model mobility_arg, double m_t0, + double m_dt_integration) + : Node(node_id, property_arg, m_t0, m_dt_integration) + , stay_duration(duration) + , mobility(mobility_arg, m_t0, m_dt_integration) + { + } + + template + NodeDetailed(int node_id, double duration, Args&&... args) + : Node(node_id, std::forward(args)...) + , stay_duration(duration) + , mobility(std::forward(args)...) + { + } + + NodeDetailed(int node_id, double duration, NodePropertyT property_arg, NodePropertyT mobility_pt_arg) + : Node(node_id, property_arg) + , stay_duration(duration) + , mobility(mobility_pt_arg) + { + } + + double stay_duration; + NodePropertyT mobility; +}; + +template +struct EdgeDetailed : Edge { + template + EdgeDetailed(size_t start, size_t end, Args&&... args) + : Edge(start, end, std::forward(args)...) + , traveltime(0.) + , path{static_cast(start), static_cast(end)} + { + } + + template + EdgeDetailed(size_t start, size_t end, double t_travel, Args&&... args) + : Edge(start, end, std::forward(args)...) + , traveltime(t_travel) + , path{static_cast(start), static_cast(end)} + { + } + + template + EdgeDetailed(size_t start, size_t end, double t_travel, std::vector path_mobility, Args&&... args) + : Edge(start, end, std::forward(args)...) + , traveltime(t_travel) + , path(path_mobility) + { + } + double traveltime; + std::vector path; +}; + +template +class GraphDetailed : public Graph +{ +public: + using Graph::Graph; + + template + NodeDetailed& add_node(int id, double duration_stay, ModelType& model1, ModelType& model2) + { + m_nodes.emplace_back(id, duration_stay, model1, model2); + return m_nodes.back(); + } + + template + NodeDetailed& add_node(int id, double duration_stay, ModelType& model1, ModelType& model2, + double m_t0, double m_dt_integration) + { + m_nodes.emplace_back(id, duration_stay, model1, model2, m_t0, m_dt_integration); + return m_nodes.back(); + } + + EdgeDetailed& add_edge(size_t start_node_idx, size_t end_node_idx, double traveltime, + mio::MigrationParameters& args) + { + assert(m_nodes.size() > start_node_idx && m_nodes.size() > end_node_idx); + return *insert_sorted_replace(m_edges, + EdgeDetailed(start_node_idx, end_node_idx, traveltime, args), + [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx + ? e1.end_node_idx < e2.end_node_idx + : e1.start_node_idx < e2.start_node_idx; + }); + } + + template + EdgeDetailed& add_edge(size_t start_node_idx, size_t end_node_idx, double traveltime, + std::vector path, Args&&... args) + { + assert(m_nodes.size() > start_node_idx && m_nodes.size() > end_node_idx); + return *insert_sorted_replace( + m_edges, + EdgeDetailed(start_node_idx, end_node_idx, traveltime, path, std::forward(args)...), + [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx + : e1.start_node_idx < e2.start_node_idx; + }); + } + +private: + std::vector> m_nodes; + std::vector> m_edges; +}; + +/** + * @brief Sets the graph nodes for counties or districts. + * Reads the node ids which could refer to districts or counties and the epidemiological + * data from json files and creates one node for each id. Every node contains a model. + * @param[in] params Model Parameters that are used for every node. + * @param[in] start_date Start date for which the data should be read. + * @param[in] end_data End date for which the data should be read. + * @param[in] data_dir Directory that contains the data files. + * @param[in] population_data_path Path to json file containing the population data. + * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. + * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] read_func Function that reads input data for german counties and sets Model compartments. + * @param[in] node_func Function that returns the county ids. + * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. + * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. + * @param[in] tnt_capacity_factor Factor for test and trace capacity. + * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. + * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. + */ +template +IOResult set_nodes_detailed(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, + const std::string& population_data_path, const std::string& stay_times_data_path, + bool is_node_for_county, GraphDetailed& params_graph, + ReadFunction&& read_func, NodeIdFunction&& node_func, + const std::vector& scaling_factor_inf, double scaling_factor_icu, + double tnt_capacity_factor, int num_days = 0, bool export_time_series = false) +{ + + BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); + BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county)); + + std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); + + for (auto& node : nodes) { + node.parameters = params; + } + BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), + num_days, export_time_series)); + + for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { + + auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; + + //local parameters + auto& tnt_value = nodes[node_idx].parameters.template get(); + tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); + tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); + + //holiday periods + auto id = int(mio::regions::CountyId(node_ids[node_idx])); + auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); + auto& contacts = nodes[node_idx].parameters.template get(); + contacts.get_school_holidays() = + std::vector>(holiday_periods.size()); + std::transform( + holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { + return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), + mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); + }); + + //uncertainty in populations + for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { + for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { + auto& compartment_value = nodes[node_idx].populations[{i, j}]; + compartment_value = + mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); + compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), + 1.1 * double(compartment_value))); + } + } + + // Add mobility node + auto mobility = nodes[node_idx]; + mobility.populations.set_total(0); + + params_graph.add_node(node_ids[node_idx], duration_stay((Eigen::Index)node_idx), nodes[node_idx], mobility); + } + return success(); +} + +/** + * @brief Sets the graph edges. + * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. + * @param[in] travel_times_path Path to txt file containing the travel times between counties. + * @param[in] mobility_data_path Path to txt file containing the commuting matrices. + * @param[in] travelpath_path Path to txt file containing the paths between counties. + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] migrating_compartments Compartments that commute. + * @param[in] contact_locations_size Number of contact locations. + * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. + */ +template +IOResult +set_edges_detailed(const std::string& travel_times_path, const std::string mobility_data_path, + const std::string& travelpath_path, GraphDetailed& params_graph, + std::initializer_list& migrating_compartments, size_t contact_locations_size, + std::vector commuting_weights = std::vector{}, + ScalarType theshold_edges = 4e-5) +{ + BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_path)); + BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_path)); + BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travelpath_path)); + + for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { + for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { + auto& populations = params_graph.nodes()[county_idx_i].property.populations; + + // mobility coefficients have the same number of components as the contact matrices. + // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) + auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); + auto num_age_groups = (size_t)params_graph.nodes()[county_idx_i].property.parameters.get_num_groups(); + commuting_weights = + (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); + + //commuters + auto working_population = 0.0; + auto min_commuter_age = mio::AgeGroup(2); + auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; + } + auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + for (auto compartment : migrating_compartments) { + auto coeff_index = populations.get_flat_index({age, compartment}); + mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = + commuter_coeff_ij * commuting_weights[size_t(age)]; + } + } + + auto path = path_mobility[county_idx_i][county_idx_j]; + if (static_cast(path[0]) != county_idx_i || + static_cast(path[path.size() - 1]) != county_idx_j) + std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; + + //only add edges with mobility above thresholds for performance + if (commuter_coeff_ij > theshold_edges) { + params_graph.add_edge(county_idx_i, county_idx_j, travel_times(county_idx_i, county_idx_j), + path_mobility[county_idx_i][county_idx_j], std::move(mobility_coeffs)); + } + } + } + + return success(); +} + +template +class MigrationEdgeDetailed : public MigrationEdge +{ +public: + using MigrationEdge::MigrationEdge; + + template + void apply_migration(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to, int mode) + { + + if (mode == 0) { + //normal daily migration + this->m_migrated.add_time_point(t, (node_from.get_last_state().array() * + this->m_parameters.get_coefficients().get_matrix_at(t).array() * + get_migration_factors(node_from, t, node_from.get_last_state()).array()) + .matrix()); + this->m_return_times.add_time_point(t + dt); + move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), + node_to.get_result().get_last_value()); + } + // change county of migrated + else if (mode == 1) { + // update status of migrated before moving to next county + auto& integrator_node = node_from.get_simulation().get_integrator(); + update_status_migrated(this->m_migrated.get_last_value(), node_from.get_simulation(), integrator_node, + node_from.get_result().get_last_value(), t, dt); + move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), + node_to.get_result().get_last_value()); + } + // option for last time point to remove time points + else if (mode == 2) { + Eigen::Index idx = this->m_return_times.get_num_time_points() - 1; + auto& integrator_node = node_from.get_simulation().get_integrator(); + update_status_migrated(this->m_migrated[idx], node_from.get_simulation(), integrator_node, + node_from.get_result().get_last_value(), t, dt); + + move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), + node_to.get_result().get_last_value()); + + for (Eigen::Index i = this->m_return_times.get_num_time_points() - 1; i >= 0; --i) { + if (this->m_return_times.get_time(i) <= t) { + this->m_migrated.remove_time_point(i); + this->m_return_times.remove_time_point(i); + } + } + } + // just update status of migrated + else if (mode == 3) { + Eigen::Index idx = this->m_return_times.get_num_time_points() - 1; + auto& integrator_node = node_from.get_simulation().get_integrator(); + update_status_migrated(this->m_migrated[idx], node_from.get_simulation(), integrator_node, + node_from.get_result().get_last_value(), t, dt); + + move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), + node_from.get_result().get_last_value()); + } + else { + std::cout << "Invalid input mode. Should be 0 or 1." + << "\n"; + } + } +}; + +/** + * edge functor for migration simulation. + * @see MigrationEdge::apply_migration + */ +template +void apply_migration(FP t, FP dt, MigrationEdgeDetailed& migrationEdgeDetailed, SimulationNode& node_from, + SimulationNode& node_to, int mode) +{ + migrationEdgeDetailed.apply_migration(t, dt, node_from, node_to, mode); +} + +template +class ParameterStudyDetailed : public ParameterStudy +{ +public: + /** + * The type of simulation of a single node of the graph. + */ + using Simulation = S; + /** + * The Graph type that stores the parametes of the simulation. + * This is the input of ParameterStudies. + */ + using ParametersGraphDetailed = mio::GraphDetailed>; + /** + * The Graph type that stores simulations and their results of each run. + * This is the output of ParameterStudies for each run. + */ + using SimulationGraphDetailed = mio::GraphDetailed, mio::MigrationEdge>; + + /** + * create study for graph of compartment models. + * @param graph graph of parameters + * @param t0 start time of simulations + * @param tmax end time of simulations + * @param graph_sim_dt time step of graph simulation + * @param num_runs number of runs + */ + ParameterStudyDetailed(const ParametersGraphDetailed& graph, double t0, double tmax, double graph_sim_dt, + size_t num_runs) + : ParameterStudy(graph, t0, tmax, graph_sim_dt, num_runs) + , m_graph(graph) + , m_num_runs(num_runs) + , m_t0{t0} + , m_tmax{tmax} + , m_dt_graph_sim(graph_sim_dt) + { + } + + /* + * @brief Carry out all simulations in the parameter study. + * Save memory and enable more runs by immediately processing and/or discarding the result. + * The result processing function is called when a run is finished. It receives the result of the run + * (a SimulationGraphDetailed object) and an ordered index. The values returned by the result processing function + * are gathered and returned as a list. + * This function is parallelized if memilio is configured with MEMILIO_ENABLE_MPI. + * The MPI processes each contribute a share of the runs. The sample function and result processing function + * are called in the same process that performs the run. The results returned by the result processing function are + * gathered at the root process and returned as a list by the root in the same order as if the programm + * were running sequentially. Processes other than the root return an empty list. + * @param sample_graph Function that receives the ParametersGraph and returns a sampled copy. + * @param result_processing_function Processing function for simulation results, e.g., output function. + * @returns At the root process, a list of values per run that have been returned from the result processing function. + * At all other processes, an empty list. + * @tparam SampleGraphFunction Callable type, accepts instance of ParametersGraph. + * @tparam HandleSimulationResultFunction Callable type, accepts instance of SimulationGraphDetailed and an index of type size_t. + */ + template + std::vector> + run(SampleGraphFunction sample_graph, HandleSimulationResultFunction result_processing_function) + { + int num_procs, rank; +#ifdef MEMILIO_ENABLE_MPI + MPI_Comm_size(mpi::get_world(), &num_procs); + MPI_Comm_rank(mpi::get_world(), &rank); +#else + num_procs = 1; + rank = 0; +#endif + + //The ParameterDistributions used for sampling parameters use thread_local_rng() + //So we set our own RNG to be used. + //Assume that sampling uses the thread_local_rng() and isn't multithreaded + this->m_rng.synchronize(); + thread_local_rng() = this->m_rng; + + auto run_distribution = this->distribute_runs(m_num_runs, num_procs); + auto start_run_idx = + std::accumulate(run_distribution.begin(), run_distribution.begin() + size_t(rank), size_t(0)); + auto end_run_idx = start_run_idx + run_distribution[size_t(rank)]; + + std::vector> + ensemble_result; + ensemble_result.reserve(m_num_runs); + + for (size_t run_idx = start_run_idx; run_idx < end_run_idx; run_idx++) { + log(LogLevel::info, "ParameterStudies: run {}", run_idx); + + //prepare rng for this run by setting the counter to the right offset + //Add the old counter so that this call of run() produces different results + //from the previous call + auto run_rng_counter = + this->m_rng.get_counter() + + rng_totalsequence_counter(static_cast(run_idx), Counter(0)); + thread_local_rng().set_counter(run_rng_counter); + + //sample + auto sim = create_sampled_sim(sample_graph); + log(LogLevel::info, "ParameterStudies: Generated {} random numbers.", + (thread_local_rng().get_counter() - run_rng_counter).get()); + + //perform run + sim.advance(m_tmax); + + //handle result and store + ensemble_result.emplace_back(result_processing_function(std::move(sim).get_graph(), run_idx)); + } + + //Set the counter of our RNG so that future calls of run() produce different parameters. + this->m_rng.set_counter(this->m_rng.get_counter() + + rng_totalsequence_counter(m_num_runs, Counter(0))); + +#ifdef MEMILIO_ENABLE_MPI + //gather results + if (rank == 0) { + for (int src_rank = 1; src_rank < num_procs; ++src_rank) { + int bytes_size; + MPI_Recv(&bytes_size, 1, MPI_INT, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); + ByteStream bytes(bytes_size); + MPI_Recv(bytes.data(), bytes.data_size(), MPI_BYTE, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); + + auto src_ensemble_results = deserialize_binary(bytes, Tag{}); + if (!src_ensemble_results) { + log_error("Error receiving ensemble results from rank {}.", src_rank); + } + std::copy(src_ensemble_results.value().begin(), src_ensemble_results.value().end(), + std::back_inserter(ensemble_result)); + } + } + else { + auto bytes = serialize_binary(ensemble_result); + auto bytes_size = int(bytes.data_size()); + MPI_Send(&bytes_size, 1, MPI_INT, 0, 0, mpi::get_world()); + MPI_Send(bytes.data(), bytes.data_size(), MPI_BYTE, 0, 0, mpi::get_world()); + ensemble_result.clear(); //only return root process + } +#endif + + return ensemble_result; + } + +private: + //sample parameters and create simulation + template + mio::GraphSimulationDetailed create_sampled_sim(SampleGraphFunction sample_graph) + { + SimulationGraphDetailed sim_graph; + + auto sampled_graph = sample_graph(m_graph); + for (auto&& node : sampled_graph.nodes()) { + sim_graph.add_node(node.id, node.stay_duration, node.property, node.mobility, m_t0, this->m_dt_integration); + } + for (auto&& edge : sampled_graph.edges()) { + sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.traveltime, edge.property); + } + return make_migration_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); + } + +private: + // Stores Graph with the names and ranges of all parameters + ParametersGraphDetailed m_graph; + size_t m_num_runs; + double m_t0; + double m_tmax; + double m_dt_graph_sim; +}; + +/** + * @brief number of migrated people when they return according to the model. + * E.g. during the time in the other node, some people who left as susceptible will return exposed. + * Implemented for general compartmentmodel simulations, overload for your custom model if necessary + * so that it can be found with argument-dependent lookup, i.e. in the same namespace as the model. + * @param migrated number of people that migrated as input, number of people that return as output + * @param sim Simulation that is used for the migration + * @param integrator Integrator that is used for the estimation. Has to be a one-step scheme. + * @param total total population in the node that the people migrated to. + * @param t time of migration + * @param dt timestep + */ +// template ::value>> +// void calculate_migration_returns(Eigen::Ref::Vector> migrated, const Sim& sim, +// Eigen::Ref::Vector> total, FP t, FP dt) +// { +// auto y0 = migrated.eval(); +// auto y1 = migrated; +// EulerIntegratorCore().step( +// [&](auto&& y, auto&& t_, auto&& dydt) { +// sim.get_model().get_derivatives(total, y, t_, dydt); +// }, +// y0, t, dt, y1); +// auto flows_model = sim.get_model().get_flow_values(); +// flows_model *= dt; +// sim.get_model().set_flow_values(flows_model); +// } + +template +using Vector = Eigen::Matrix; + +template +void move_migrated(Eigen::Ref> migrated, Eigen::Ref> results_from, + Eigen::Ref> results_to) +{ + const auto group = 6; + const auto num_comparts = results_to.size() / group; + + // check for negative values in migrated + for (Eigen::Index j = 0; j < migrated.size(); ++j) { + if (migrated(j) < -1e-8) { + std::cout << "Negative Value in migration detected. With value" << migrated(j) << "\n"; + auto compart = j % num_comparts; + auto curr_age_group = int(j / num_comparts); + auto indx_begin = curr_age_group * num_comparts; + auto indx_end = (curr_age_group + 1) * num_comparts; + // calculate max index in indx boundaries + Eigen::Index max_index = indx_begin; + for (Eigen::Index i = indx_begin; i < indx_end; ++i) { + if (migrated(i) > migrated(max_index)) { + max_index = i; + } + } + // we assume that the solution from migrated is bettter because there is contact with other nodes + migrated(max_index) = migrated(max_index) + migrated(j); + migrated(j) = migrated(j) - migrated(j); + } + } + + // calc sum of migrated and from + auto sum_migrated = migrated.sum(); + auto sum_from = results_from.sum(); + if (std::abs(sum_migrated - sum_from) < 1e-8) { + results_from = migrated; + } + else { + Eigen::VectorXd remaining_after_return = (results_from - migrated).eval(); + // auto remaining_after_return_as_vector = std::vector( + // remaining_after_return.data(), remaining_after_return.data() + remaining_after_return.size()); + for (Eigen::Index j = 0; j < results_to.size(); ++j) { + if (remaining_after_return(j) < -1e-8) { + auto compart = j % num_comparts; + auto curr_age_group = int(j / num_comparts); + auto indx_begin = curr_age_group * num_comparts; + auto indx_end = (curr_age_group + 1) * num_comparts; + // calculate max index in indx boundaries + Eigen::Index max_index = indx_begin; + for (Eigen::Index i = indx_begin; i < indx_end; ++i) { + if (remaining_after_return(i) > remaining_after_return(max_index)) { + max_index = i; + } + } + + // its possible that the max value in the boundaries is not enough to fill the negative value. + // Therefore we have to find multiple max values + while (remaining_after_return(max_index) + remaining_after_return(j) < -1e-10) { + + // calculate sum between indx_begin and indx_end + double result_from_sum_group = 0; + double result_migration_sum_group = 0; + for (Eigen::Index i = indx_begin; i < indx_end; ++i) { + result_from_sum_group += results_from(i); + result_migration_sum_group += migrated(i); + } + auto diff = result_from_sum_group - result_migration_sum_group; + if (diff < -1e-8) { + std::cout << "Sum of results_from is smaller than sum of migrated. Diff is " + << result_from_sum_group - result_migration_sum_group << "\n"; + // transfer values from migrated to results_from + for (Eigen::Index i = indx_begin; i < indx_end; ++i) { + results_from(i) = migrated(i); + } + } + + results_from(j) = results_from(j) + remaining_after_return(max_index); + results_from(max_index) = results_from(max_index) - remaining_after_return(max_index); + remaining_after_return = (results_from - migrated).eval(); + + max_index = indx_begin; + for (Eigen::Index i = indx_begin; i < indx_end; ++i) { + if (remaining_after_return(i) > remaining_after_return(max_index)) { + max_index = i; + } + } + if (max_index == indx_begin && remaining_after_return(max_index) == 0) { + std::cout << "Fixing negative Value in migration not possible." + << "\n"; + } + } + + // we assume that the solution from migrated is bettter because there is contact with other nodes + results_from(j) = results_from(j) - remaining_after_return(j); + results_from(max_index) = results_from(max_index) + remaining_after_return(j); + remaining_after_return = (results_from - migrated).eval(); + } + } + } + + results_from -= migrated; + results_to += migrated; +} + +// template +// void MigrationEdgeDetailed::apply_migration(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to, +// int mode) + +/** + * create a migration simulation. + * After every second time step, for each edge a portion of the population corresponding to the coefficients of the edge + * moves from one node to the other. In the next timestep, the migrated population return to their "home" node. + * Returns are adjusted based on the development in the target node. + * @param t0 start time of the simulation + * @param dt time step between migrations + * @param graph set up for migration simulation + * @{ + */ +template +GraphSimulationDetailed, MigrationEdge>> +make_migration_sim(double t0, double dt, const GraphDetailed, MigrationEdge>& graph) +{ + return make_graph_sim_detailed(t0, dt, graph, &evolve_model, &apply_migration); +} + +template +GraphSimulationDetailed, MigrationEdge>> +make_migration_sim(double t0, double dt, GraphDetailed, MigrationEdge>&& graph) +{ + return make_graph_sim_detailed(t0, dt, std::move(graph), &evolve_model, &apply_migration); +} + +} // namespace mio + +#endif //METAPOPULATION_MOBILITY_DETAILED_H \ No newline at end of file diff --git a/cpp/memilio/mobility/metapopulation_mobility_instant.h b/cpp/memilio/mobility/metapopulation_mobility_instant.h index 2cea9c59ad..3baf6389bf 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_instant.h +++ b/cpp/memilio/mobility/metapopulation_mobility_instant.h @@ -295,7 +295,7 @@ class MigrationEdge template void apply_migration(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to); -private: +protected: MigrationParameters m_parameters; TimeSeries m_migrated; TimeSeries m_return_times; From 96a7154dd615e7b556738434d529c6b99dbf5322 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Sat, 13 Jul 2024 04:47:11 +0200 Subject: [PATCH 02/26] mobility detailed now running. --- cpp/examples/CMakeLists.txt | 6 +- cpp/examples/ode_secir_detailed_mobility.cpp | 104 -- cpp/examples/ode_seir_detailed_mobility.cpp | 87 + cpp/memilio/compartments/flow_model.h | 10 + cpp/memilio/mobility/graph_simulation.h | 486 +----- .../metapopulation_mobility_detailed.h | 1448 +++++++++++------ 6 files changed, 1016 insertions(+), 1125 deletions(-) delete mode 100644 cpp/examples/ode_secir_detailed_mobility.cpp create mode 100644 cpp/examples/ode_seir_detailed_mobility.cpp diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index ec5aa633b8..bcfac5f8e7 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -72,9 +72,9 @@ add_executable(ode_secirvvs_example ode_secirvvs.cpp) target_link_libraries(ode_secirvvs_example PRIVATE memilio ode_secirvvs) target_compile_options(ode_secirvvs_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) -add_executable(ode_secir_detailed_mobility ode_secir_detailed_mobility.cpp) -target_link_libraries(ode_secir_detailed_mobility PRIVATE memilio ode_secir) -target_compile_options(ode_secir_detailed_mobility PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(ode_seir_detailed_mobility ode_seir_detailed_mobility.cpp) +target_link_libraries(ode_seir_detailed_mobility PRIVATE memilio ode_seir) +target_compile_options(ode_seir_detailed_mobility PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) add_executable(ode_secir_ageres_example ode_secir_ageres.cpp) target_link_libraries(ode_secir_ageres_example PRIVATE memilio ode_secir) diff --git a/cpp/examples/ode_secir_detailed_mobility.cpp b/cpp/examples/ode_secir_detailed_mobility.cpp deleted file mode 100644 index 0e378992b8..0000000000 --- a/cpp/examples/ode_secir_detailed_mobility.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (C) 2020-2024 MEmilio -* -* Authors: Daniel Abele -* -* Contact: Martin J. Kuehn -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -#include "memilio/mobility/metapopulation_mobility_detailed.h" -#include "memilio/io/mobility_io.h" -#include "memilio/compartments/parameter_studies.h" -#include "ode_secir/parameter_space.h" -#include "ode_secir/parameters_io.h" -#include - -int main() -{ - const auto t0 = 0.; - const auto dt = 0.5; - const auto tmax = 10.; - - ScalarType cont_freq = 10; // see Polymod study - - ScalarType nb_total_t0 = 10000, nb_exp_t0 = 100, nb_inf_t0 = 50, nb_car_t0 = 50, nb_hosp_t0 = 20, nb_icu_t0 = 10, - nb_rec_t0 = 10, nb_dead_t0 = 0; - - mio::osecir::Model model(1); - mio::AgeGroup nb_groups = model.parameters.get_num_groups(); - ScalarType fact = 1.0 / (ScalarType)(size_t)nb_groups; - - auto& params = model.parameters; - - params.set>(std::numeric_limits::max()); - params.set(0); - params.set>(0); - - for (auto i = mio::AgeGroup(0); i < nb_groups; i++) { - params.get>()[i] = 3.2; - params.get>()[i] = 2.0; - params.get>()[i] = 6.; - params.get>()[i] = 12; - params.get>()[i] = 8; - - model.populations[{i, mio::osecir::InfectionState::Exposed}] = fact * nb_exp_t0; - model.populations[{i, mio::osecir::InfectionState::InfectedNoSymptoms}] = fact * nb_car_t0; - model.populations[{i, mio::osecir::InfectionState::InfectedNoSymptomsConfirmed}] = 0; - model.populations[{i, mio::osecir::InfectionState::InfectedSymptoms}] = fact * nb_inf_t0; - model.populations[{i, mio::osecir::InfectionState::InfectedSymptomsConfirmed}] = 0; - model.populations[{i, mio::osecir::InfectionState::InfectedSevere}] = fact * nb_hosp_t0; - model.populations[{i, mio::osecir::InfectionState::InfectedCritical}] = fact * nb_icu_t0; - model.populations[{i, mio::osecir::InfectionState::Recovered}] = fact * nb_rec_t0; - model.populations[{i, mio::osecir::InfectionState::Dead}] = fact * nb_dead_t0; - model.populations.set_difference_from_group_total({i, mio::osecir::InfectionState::Susceptible}, - fact * nb_total_t0); - - params.get>()[i] = 0.05; - params.get>()[i] = 0.67; - params.get>()[i] = 0.09; - params.get>()[i] = 0.25; - params.get>()[i] = 0.2; - params.get>()[i] = 0.25; - params.get>()[i] = 0.3; - } - - params.apply_constraints(); - - mio::ContactMatrixGroup& contact_matrix = params.get>(); - contact_matrix[0] = - mio::ContactMatrix(Eigen::MatrixXd::Constant((size_t)nb_groups, (size_t)nb_groups, fact * cont_freq)); - - mio::GraphDetailed>>, - mio::MigrationEdgeDetailed> - g; - for (size_t county_id = 0; county_id < 3; county_id++) { - g.add_node(county_id, model, 0.0); - } - - // Graph is always complete here - for (size_t county_idx_i = 0; county_idx_i < g.nodes().size(); ++county_idx_i) { - for (size_t county_idx_j = 0; county_idx_j < g.nodes().size(); ++county_idx_j) { - if (county_idx_i == county_idx_j) - continue; - g.add_edge(county_idx_i, county_idx_j, - Eigen::VectorXd::Constant( - (size_t)mio::osecir::InfectionState::Count * static_cast(nb_groups), 0.01)); - } - } - - auto sim = mio::make_migration_sim(t0, dt, std::move(g)); - sim.advance(tmax); - - return 0; -} diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp new file mode 100644 index 0000000000..dc2c0f4fbb --- /dev/null +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -0,0 +1,87 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "ode_seir/model.h" +#include "ode_seir/infection_state.h" +#include "ode_seir/parameters.h" +#include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/mobility/metapopulation_mobility_detailed.h" +#include "memilio/compartments/simulation.h" +#include "memilio/data/analyze_result.h" + +int main() +{ + const auto t0 = 0.; + const auto tmax = 10.; + const auto dt = 0.5; //time step of migration, daily migration every second step + + mio::oseir::Model<> model(1); + + // set population + model.populations[{mio::AgeGroup(0), mio::oseir::InfectionState::Susceptible}] = 10000; + + // set transition times + model.parameters.set>(1); + model.parameters.set>(1); + + // set contact matrix + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>().get_cont_freq_mat(); + contact_matrix[0].get_baseline().setConstant(2.7); + + //two mostly identical groups + auto model_group1 = model; + auto model_group2 = model; + + //some contact restrictions in group 1 + mio::ContactMatrixGroup& contact_matrix1 = + model_group1.parameters.get>().get_cont_freq_mat(); + contact_matrix1[0].add_damping(0.5, mio::SimulationTime(5)); + + //infection starts in group 1 + model_group1.populations[{mio::AgeGroup(0), mio::oseir::InfectionState::Susceptible}] = 9990; + model_group1.populations[{mio::AgeGroup(0), mio::oseir::InfectionState::Exposed}] = 10; + + auto sim1 = mio::Simulation>(model_group1, t0, dt); + auto sim2 = mio::Simulation>(model_group2, t0, dt); + double stay_time_1 = 0.3; + double stay_time_2 = 0.3; + mio::ExtendedGraph>> g; + g.add_node(1, sim1, sim2, stay_time_1); + g.add_node(2, sim1, sim2, stay_time_2); + + double traveltime = 0.1; + std::vector path1 = {0, 1}; + std::vector path2 = {1, 0}; + g.add_edge(0, 1, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path1); + g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); + + auto sim = mio::make_extended_migration_sim(t0, dt, std::move(g)); + + sim.advance(tmax); + // results node 1 + std::cout << "Results node 1" << std::endl; + auto interpolated_sim1 = mio::interpolate_simulation_result(sim.get_graph().nodes()[1].property.base_sim.get_result()); + interpolated_sim1.print_table({"S", "E", "I", "R"}); + + // results node 1 mobility_sim + std::cout << "Mobility results node 1" << std::endl; + auto interpolated_sim1_mobility = sim.get_graph().nodes()[1].property.mobility_sim.get_result(); + interpolated_sim1_mobility.print_table({"S", "E", "I", "R"}); + return 0; +} diff --git a/cpp/memilio/compartments/flow_model.h b/cpp/memilio/compartments/flow_model.h index 41581633cd..b3cc554474 100644 --- a/cpp/memilio/compartments/flow_model.h +++ b/cpp/memilio/compartments/flow_model.h @@ -203,6 +203,16 @@ class FlowModel : public CompartmentalModel return index_of_type_v, Flows>; } + Eigen::VectorXd get_flow_values() const + { + return m_flow_values; + } + + void set_flow_values(const Eigen::VectorXd& flows) + { + m_flow_values = flows; + } + private: mutable Vector m_flow_values; ///< Cache to avoid allocation in get_derivatives (using get_flows). diff --git a/cpp/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index dbe71ea083..13ce3a0e0c 100644 --- a/cpp/memilio/mobility/graph_simulation.h +++ b/cpp/memilio/mobility/graph_simulation.h @@ -249,484 +249,7 @@ class GraphSimulationStochastic RandomNumberGenerator m_rng; }; -template > -class GraphSimulationDetailed -{ - -public: - using edge_function = edge_f; - using node_function = std::function; - - GraphSimulationDetailed(double t0, double dt, const GraphDetailed& g, const node_function& node_func, - const edge_function&& edge_func) - : m_dt(dt) - , m_t(double(t0)) - , m_graph(g) - , m_node_func(node_func) - , m_edge_func(edge_func) - { - } - - GraphSimulationDetailed(double t0, double dt, GraphDetailed&& g, const node_function& node_func, - const edge_function&& edge_func) - : m_dt(dt) - , m_t(double(t0)) - , m_graph(std::move(g)) - , m_node_func(node_func) - , m_edge_func(edge_func) - { - } - - inline double round_second_decimal(double x) - { - return std::round(x * 100.0) / 100.0; - } - - double get_t() const - { - return m_t; - } - - GraphDetailed& get_graph() & - { - return m_graph; - } - - const GraphDetailed& get_graph() const& - { - return m_graph; - } - - GraphDetailed&& get_graph() && - { - return std::move(m_graph); - } - - void advance(double t_max = 1.0) - { - ScalarType dt_first_mobility = - std::accumulate(m_graph.edges().begin(), m_graph.edges().end(), std::numeric_limits::max(), - [&](ScalarType current_min, const auto& e) { - auto traveltime_per_region = round_second_decimal(e.traveltime / e.path.size()); - if (traveltime_per_region < 0.01) - traveltime_per_region = 0.01; - auto start_mobility = - round_second_decimal(1 - 2 * (traveltime_per_region * e.path.size()) - - m_graph.nodes()[e.end_node_idx].stay_duration); - if (start_mobility < 0) { - start_mobility = 0.; - } - return std::min(current_min, start_mobility); - }); - - // set population to zero in mobility nodes before starting - for (auto& n : m_graph.nodes()) { - n.mobility.get_result().get_last_value().setZero(); - } - - auto min_dt = 0.01; - double t_begin = m_t - 1.; - - // calc schedule for each edge - precompute_schedule(); - while (m_t - epsilon < t_max) { - // auto start = std::chrono::system_clock::now(); - - t_begin += 1; - if (m_t + dt_first_mobility > t_max) { - dt_first_mobility = t_max - m_t; - for (auto& n : m_graph.nodes()) { - m_node_func(m_t, dt_first_mobility, n.property); - } - break; - } - - for (auto& node : m_graph.nodes()) { - node.mobility.get_simulation().set_integrator(std::make_shared>()); - } - - size_t indx_schedule = 0; - while (t_begin + 1 > m_t + 1e-10) { - advance_edges(indx_schedule); - - // first we integrate the nodes in time. Afterwards the update on the edges is done. - // We start with the edges since the values for t0 are given. - advance_local_nodes(indx_schedule); - advance_mobility_nodes(indx_schedule); - - indx_schedule++; - m_t += min_dt; - } - // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate - // the state of the indivuals moving between different counties. - // Therefore there can be differences with the states given by the integrator used for the mobility node. - for (auto& n : m_graph.nodes()) { - n.mobility.get_result().get_last_value().setZero(); - } - } - } - -private: - GraphDetailed m_graph; - double m_t; - double m_dt; - node_function m_node_func; - edge_function m_edge_func; - - // describes the schedule for each edge, i.e. which node is visited at which time step - std::vector> schedule_edges; - - // defines if people from a edge are currently in a mobility node. This is necessary to determine the correct - // position in the schedule. Otherwise we could add a specific identifier in schedule_edges. - std::vector> mobility_schedule_edges; - - // describes the syncronization steps which are necessary for the integrator in the edges and nodes. - std::vector> mb_int_schedule; - std::vector> ln_int_schedule; - - // defines the edges on which mobility takes place for each time step - std::vector> edges_mobility; - - // same for the nodes - std::vector> nodes_mobility; - std::vector> nodes_mobility_m; - - // first mobility activites per edge - std::vector first_mobility; - - const double epsilon = std::numeric_limits::epsilon(); - - void precompute_schedule() - { - // print transi - const size_t timesteps = 100; - schedule_edges.reserve(m_graph.edges().size()); - mobility_schedule_edges.reserve(m_graph.edges().size()); - - for (auto& e : m_graph.edges()) { - // 100 since we round to second decimal - std::vector schedule(timesteps, 0.); - std::vector is_mobility_node(timesteps, false); - - double traveltime_per_region = round_second_decimal(e.traveltime / e.path.size()); - - if (traveltime_per_region < 0.01) - traveltime_per_region = 0.01; - - double start_mobility = round_second_decimal(0 + 1 - 2 * (traveltime_per_region * e.path.size()) - - m_graph.nodes()[e.end_node_idx].stay_duration); - if (start_mobility < 0.0) { - start_mobility = 0.; - } - - double arrive_at = start_mobility + traveltime_per_region * e.path.size(); - - std::fill(schedule.begin(), schedule.begin() + static_cast((start_mobility + epsilon) * 100), - e.start_node_idx); - - // all values true between start mob und arrive - std::fill(is_mobility_node.begin() + static_cast((start_mobility + epsilon) * 100), - is_mobility_node.begin() + static_cast((arrive_at + epsilon) * 100), true); - int count_true = std::count(is_mobility_node.begin(), is_mobility_node.end(), true); - size_t current_index = static_cast((start_mobility + epsilon) * 100); - for (size_t county : e.path) { - std::fill(schedule.begin() + current_index, - schedule.begin() + current_index + - static_cast((traveltime_per_region + epsilon) * 100), - county); - current_index += static_cast((traveltime_per_region + epsilon) * 100); - } - - // stay in destination county - std::fill(schedule.begin() + current_index, schedule.end() - count_true, e.path[e.path.size() - 1]); - - // revert trip for return. - std::fill(is_mobility_node.end() - count_true, is_mobility_node.end(), true); - - auto first_true = std::find(is_mobility_node.begin(), is_mobility_node.end(), true); - auto last_true = std::find(is_mobility_node.rbegin(), is_mobility_node.rend(), true); - - // If all values are false, we get an error - if (first_true != is_mobility_node.end() && last_true != is_mobility_node.rend()) { - int first_index = std::distance(is_mobility_node.begin(), first_true); - std::vector path_reversed(schedule.begin() + first_index, - schedule.begin() + first_index + count_true); - std::reverse(path_reversed.begin(), path_reversed.end()); - std::copy(path_reversed.begin(), path_reversed.end(), schedule.end() - count_true); - schedule_edges.push_back(schedule); - mobility_schedule_edges.push_back(is_mobility_node); - } - else { - log_error("Error in creating schedule."); - } - } - - // iterate over nodes - size_t indx_node = 0; - for (auto& n : m_graph.nodes()) { - // local node and automatical add zero, since we want to start at t=0 and start with integrating all nodes to the next dt - std::vector order_local_node = {0}; - std::vector indx_edges; - for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { - if (schedule_edges[indx_edge][0] == indx_node && !mobility_schedule_edges[indx_edge][0]) { - indx_edges.push_back(indx_edge); - } - } - - for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { - std::vector indx_edges_new; - for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { - if (schedule_edges[indx_edge][indx_t] == indx_node && !mobility_schedule_edges[indx_edge][indx_t]) { - indx_edges_new.push_back(indx_edge); - } - } - - if (indx_edges_new.size() != indx_edges.size() || - !std::equal(indx_edges.begin(), indx_edges.end(), indx_edges_new.begin())) { - order_local_node.push_back(indx_t); - indx_edges = indx_edges_new; - } - } - if (order_local_node[order_local_node.size() - 1] != timesteps - 1) - order_local_node.push_back(timesteps - 1); - ln_int_schedule.push_back(order_local_node); - - // mobility node - std::vector order_mobility_node; - std::vector indx_edges_mobility; - for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { - if (schedule_edges[indx_edge][0] == indx_node && mobility_schedule_edges[indx_edge][0]) { - indx_edges_mobility.push_back(indx_edge); - } - } - for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { - std::vector indx_edges_mobility_new; - for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { - if (schedule_edges[indx_edge][indx_t] == indx_node && mobility_schedule_edges[indx_edge][indx_t]) { - indx_edges_mobility_new.push_back(indx_edge); - } - } - - if (indx_edges_mobility_new.size() != indx_edges_mobility.size() || - !std::equal(indx_edges_mobility.begin(), indx_edges_mobility.end(), - indx_edges_mobility_new.begin())) { - order_mobility_node.push_back(indx_t); - indx_edges_mobility = indx_edges_mobility_new; - } - } - mb_int_schedule.push_back(order_mobility_node); - indx_node++; - } - - auto indx_edge = 0; - first_mobility.reserve(m_graph.edges().size()); - for (auto& e : m_graph.edges()) { - this->first_mobility[indx_edge] = std::distance( - mobility_schedule_edges[indx_edge].begin(), - std::find(mobility_schedule_edges[indx_edge].begin(), mobility_schedule_edges[indx_edge].end(), true)); - indx_edge++; - } - - edges_mobility.reserve(timesteps); - nodes_mobility.reserve(timesteps); - nodes_mobility_m.reserve(timesteps); - - // we handle indx_current = 0 separately since we want have added them always in the - // intregration schedule so this would lead to wrong results - // here we iterate over first mobility activities and add the edges indx when there is a start at t = 0 - std::vector temp_edges_mobility; - indx_edge = 0; - for (auto& start_time : first_mobility) { - if (start_time == 0) { - temp_edges_mobility.push_back(indx_edge); - } - indx_edge++; - } - edges_mobility.push_back(std::move(temp_edges_mobility)); - - // same for nodes - std::vector temp_nodes_mobility(m_graph.nodes().size()); - std::iota(temp_nodes_mobility.begin(), temp_nodes_mobility.end(), 0); - nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); - - std::vector temp_nodes_mobility_m; - size_t node_indx = 0; - for (auto& n : m_graph.nodes()) { - if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), 0)) { - temp_nodes_mobility_m.push_back(node_indx); - node_indx++; - } - } - nodes_mobility_m.push_back(temp_nodes_mobility_m); - - for (size_t indx_current = 1; indx_current < timesteps; ++indx_current) { - std::vector temp_edge_mobility; - indx_edge = 0; - for (auto& e : m_graph.edges()) { - auto current_node_indx = schedule_edges[indx_edge][indx_current]; - if (indx_current >= first_mobility[indx_edge]) { - if (mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(mb_int_schedule[current_node_indx].begin(), - mb_int_schedule[current_node_indx].end(), indx_current)) - temp_edge_mobility.push_back(indx_edge); - else if (!mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(ln_int_schedule[current_node_indx].begin(), - ln_int_schedule[current_node_indx].end(), indx_current)) - temp_edge_mobility.push_back(indx_edge); - } - indx_edge++; - } - edges_mobility.push_back(temp_edge_mobility); - - // reset temp_nodes_mobility - temp_nodes_mobility.clear(); - temp_nodes_mobility_m.clear(); - node_indx = 0; - for (auto& n : m_graph.nodes()) { - if (std::binary_search(ln_int_schedule[node_indx].begin(), ln_int_schedule[node_indx].end(), - indx_current)) { - temp_nodes_mobility.push_back(node_indx); - } - - if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), - indx_current)) { - temp_nodes_mobility_m.push_back(node_indx); - } - node_indx++; - } - nodes_mobility.push_back(temp_nodes_mobility); - nodes_mobility_m.push_back(temp_nodes_mobility_m); - } - } - - void advance_edges(size_t indx_schedule) - { - for (const auto& edge_indx : edges_mobility[indx_schedule]) { - auto& e = m_graph.edges()[edge_indx]; - // first mobility activity - if (indx_schedule == first_mobility[edge_indx]) { - auto& node_from = m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; - auto& node_to = m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility; - // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); - } - // next mobility activity - else if (indx_schedule > first_mobility[edge_indx]) { - auto current_node_indx = schedule_edges[edge_indx][indx_schedule]; - bool in_mobility_node = mobility_schedule_edges[edge_indx][indx_schedule]; - - // determine dt, which is equal to the last integration/syncronization point in the current node - auto integrator_schedule_row = ln_int_schedule[current_node_indx]; - if (in_mobility_node) - integrator_schedule_row = mb_int_schedule[current_node_indx]; - // search the index of indx_schedule in the integrator schedule - const size_t indx_current = std::distance( - integrator_schedule_row.begin(), - std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); - - if (integrator_schedule_row[indx_current] != indx_schedule) - std::cout << "Error in schedule." - << "\n"; - - ScalarType dt_mobility; - if (indx_current == 0) { - dt_mobility = round_second_decimal(e.traveltime / e.path.size()); - if (dt_mobility < 0.01) - dt_mobility = 0.01; - } - else { - dt_mobility = - round_second_decimal((static_cast(integrator_schedule_row[indx_current]) - - static_cast(integrator_schedule_row[indx_current - 1])) / - 100 + - epsilon); - } - - // We have two cases. Either, we want to send the individuals to the next node, or we just want - // to update their state since a syncronization step is necessary in the current node. - if ((schedule_edges[edge_indx][indx_schedule] != schedule_edges[edge_indx][indx_schedule - 1]) || - (mobility_schedule_edges[edge_indx][indx_schedule] != - mobility_schedule_edges[edge_indx][indx_schedule - 1])) { - auto& node_from = mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].mobility - : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; - - auto& node_to = mobility_schedule_edges[edge_indx][indx_schedule] - ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility - : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property; - - if (indx_schedule < mobility_schedule_edges[edge_indx].size() - 1) { - // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); - } - // else - // the last time step is handled differently since we have to close the timeseries - // m_edge_func(m_t, dt_mobility, e.property, node_from, - // m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property, 2); - } - else { - auto& node_from = mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].mobility - : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property; - - auto& node_to = mobility_schedule_edges[edge_indx][indx_schedule] - ? m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].mobility - : m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property; - - assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); - // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); - } - } - } - } - - void advance_local_nodes(size_t indx_schedule) - { - for (const auto& n_indx : nodes_mobility[indx_schedule]) { - auto& n = m_graph.nodes()[n_indx]; - const size_t indx_current = std::distance( - ln_int_schedule[n_indx].begin(), - std::lower_bound(ln_int_schedule[n_indx].begin(), ln_int_schedule[n_indx].end(), indx_schedule)); - - const size_t val_next = - (indx_current == ln_int_schedule[n_indx].size() - 1) ? 100 : ln_int_schedule[n_indx][indx_current + 1]; - const ScalarType next_dt = - round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); - m_node_func(m_t, next_dt, n.property); - } - } - - void advance_mobility_nodes(size_t indx_schedule) - { - for (const size_t& n_indx : nodes_mobility_m[indx_schedule]) { - auto& n = m_graph.nodes()[n_indx]; - const size_t indx_current = std::distance( - mb_int_schedule[n_indx].begin(), - std::lower_bound(mb_int_schedule[n_indx].begin(), mb_int_schedule[n_indx].end(), indx_schedule)); - const size_t val_next = - (indx_current == mb_int_schedule[n_indx].size() - 1) ? 100 : mb_int_schedule[n_indx][indx_current + 1]; - const ScalarType next_dt = - round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); - - // get all time points from the last integration step - auto& last_time_point = n.mobility.get_result().get_time(n.mobility.get_result().get_num_time_points() - 1); - // wenn last_time_point nicht innerhalb eines intervalls von 1-e10 von t liegt, dann setzte den letzten Zeitpunkt auf m_t - if (std::fabs(last_time_point - m_t) > 1e-10) { - n.mobility.get_result().get_last_time() = m_t; - } - m_node_func(m_t, next_dt, n.mobility); - Eigen::Index indx_min; - while (n.mobility.get_result().get_last_value().minCoeff(&indx_min) < -1e-7) { - Eigen::Index indx_max; - n.mobility.get_result().get_last_value().maxCoeff(&indx_max); - n.mobility.get_result().get_last_value()[indx_max] -= - n.mobility.get_result().get_last_value()[indx_min]; - n.mobility.get_result().get_last_value()[indx_min] = 0; - } - } - } -}; - +template auto make_graph_sim(FP t0, FP dt, Graph&& g, NodeF&& node_func, EdgeF&& edge_func) { return GraphSimulation>(t0, dt, std::forward(g), std::forward(node_func), @@ -740,12 +263,5 @@ auto make_graph_sim_stochastic(FP t0, FP dt, Graph&& g, NodeF&& node_func, EdgeF t0, dt, std::forward(g), std::forward(node_func), std::forward(edge_func)); } -template -auto make_graph_sim_detailed(double t0, double dt, GraphDetailed&& g, NodeF&& node_func, EdgeF&& edge_func) -{ - return GraphSimulationDetailed>( - t0, dt, std::forward(g), std::forward(node_func), std::forward(edge_func)); -} - } // namespace mio #endif //MIO_MOBILITY_GRAPH_SIMULATION_H diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 3751de92d6..60fc8e65ae 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -45,546 +45,77 @@ namespace mio { -template -struct NodeDetailed : Node { - template - NodeDetailed(int node_id, Args&&... args) - : Node(node_id, std::forward(args)...) - , stay_duration(0.5) - , mobility(std::forward(args)...) - { - } - - template - NodeDetailed(int node_id, double duration, Model property_arg, Model mobility_arg, double m_t0, - double m_dt_integration) - : Node(node_id, property_arg, m_t0, m_dt_integration) - , stay_duration(duration) - , mobility(mobility_arg, m_t0, m_dt_integration) - { - } - - template - NodeDetailed(int node_id, double duration, Args&&... args) - : Node(node_id, std::forward(args)...) - , stay_duration(duration) - , mobility(std::forward(args)...) - { - } - - NodeDetailed(int node_id, double duration, NodePropertyT property_arg, NodePropertyT mobility_pt_arg) - : Node(node_id, property_arg) - , stay_duration(duration) - , mobility(mobility_pt_arg) - { - } - +template +struct ExtendedNodeProperty { + Sim base_sim; + Sim mobility_sim; double stay_duration; - NodePropertyT mobility; -}; -template -struct EdgeDetailed : Edge { - template - EdgeDetailed(size_t start, size_t end, Args&&... args) - : Edge(start, end, std::forward(args)...) - , traveltime(0.) - , path{static_cast(start), static_cast(end)} + ExtendedNodeProperty(Sim sim1, Sim sim2, double t) + : base_sim(sim1) + , mobility_sim(sim2) + , stay_duration(t) { } - - template - EdgeDetailed(size_t start, size_t end, double t_travel, Args&&... args) - : Edge(start, end, std::forward(args)...) - , traveltime(t_travel) - , path{static_cast(start), static_cast(end)} - { - } - - template - EdgeDetailed(size_t start, size_t end, double t_travel, std::vector path_mobility, Args&&... args) - : Edge(start, end, std::forward(args)...) - , traveltime(t_travel) - , path(path_mobility) - { - } - double traveltime; - std::vector path; }; -template -class GraphDetailed : public Graph +template +class ExtendedMigrationEdge : public MigrationEdge { public: - using Graph::Graph; + double travel_time; + std::vector path; - template - NodeDetailed& add_node(int id, double duration_stay, ModelType& model1, ModelType& model2) + ExtendedMigrationEdge(const MigrationParameters& params, double tt, std::vector p) + : MigrationEdge(params) + , travel_time(tt) + , path(p) { - m_nodes.emplace_back(id, duration_stay, model1, model2); - return m_nodes.back(); } - template - NodeDetailed& add_node(int id, double duration_stay, ModelType& model1, ModelType& model2, - double m_t0, double m_dt_integration) + ExtendedMigrationEdge(const Eigen::VectorXd& coeffs, double tt, std::vector p) + : MigrationEdge(coeffs) + , travel_time(tt) + , path(p) { - m_nodes.emplace_back(id, duration_stay, model1, model2, m_t0, m_dt_integration); - return m_nodes.back(); } - EdgeDetailed& add_edge(size_t start_node_idx, size_t end_node_idx, double traveltime, - mio::MigrationParameters& args) + auto& get_migrated() { - assert(m_nodes.size() > start_node_idx && m_nodes.size() > end_node_idx); - return *insert_sorted_replace(m_edges, - EdgeDetailed(start_node_idx, end_node_idx, traveltime, args), - [](auto&& e1, auto&& e2) { - return e1.start_node_idx == e2.start_node_idx - ? e1.end_node_idx < e2.end_node_idx - : e1.start_node_idx < e2.start_node_idx; - }); + return this->m_migrated; } - - template - EdgeDetailed& add_edge(size_t start_node_idx, size_t end_node_idx, double traveltime, - std::vector path, Args&&... args) + auto& get_return_times() { - assert(m_nodes.size() > start_node_idx && m_nodes.size() > end_node_idx); - return *insert_sorted_replace( - m_edges, - EdgeDetailed(start_node_idx, end_node_idx, traveltime, path, std::forward(args)...), - [](auto&& e1, auto&& e2) { - return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx - : e1.start_node_idx < e2.start_node_idx; - }); + return this->m_return_times; } - -private: - std::vector> m_nodes; - std::vector> m_edges; -}; - -/** - * @brief Sets the graph nodes for counties or districts. - * Reads the node ids which could refer to districts or counties and the epidemiological - * data from json files and creates one node for each id. Every node contains a model. - * @param[in] params Model Parameters that are used for every node. - * @param[in] start_date Start date for which the data should be read. - * @param[in] end_data End date for which the data should be read. - * @param[in] data_dir Directory that contains the data files. - * @param[in] population_data_path Path to json file containing the population data. - * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. - * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] read_func Function that reads input data for german counties and sets Model compartments. - * @param[in] node_func Function that returns the county ids. - * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. - * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. - * @param[in] tnt_capacity_factor Factor for test and trace capacity. - * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. - * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. - */ -template -IOResult set_nodes_detailed(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, - const std::string& population_data_path, const std::string& stay_times_data_path, - bool is_node_for_county, GraphDetailed& params_graph, - ReadFunction&& read_func, NodeIdFunction&& node_func, - const std::vector& scaling_factor_inf, double scaling_factor_icu, - double tnt_capacity_factor, int num_days = 0, bool export_time_series = false) -{ - - BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); - BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county)); - - std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); - - for (auto& node : nodes) { - node.parameters = params; + auto& get_parameters() const + { + return this->m_parameters; } - BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), - num_days, export_time_series)); - - for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { - - auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; - - //local parameters - auto& tnt_value = nodes[node_idx].parameters.template get(); - tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); - tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); - - //holiday periods - auto id = int(mio::regions::CountyId(node_ids[node_idx])); - auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); - auto& contacts = nodes[node_idx].parameters.template get(); - contacts.get_school_holidays() = - std::vector>(holiday_periods.size()); - std::transform( - holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { - return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), - mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); - }); - - //uncertainty in populations - for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { - for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { - auto& compartment_value = nodes[node_idx].populations[{i, j}]; - compartment_value = - mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); - compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), - 1.1 * double(compartment_value))); - } - } +}; - // Add mobility node - auto mobility = nodes[node_idx]; - mobility.populations.set_total(0); +template +using ExtendedGraph = Graph, ExtendedMigrationEdge>; - params_graph.add_node(node_ids[node_idx], duration_stay((Eigen::Index)node_idx), nodes[node_idx], mobility); - } - return success(); -} +// Detect if get_migration_factors is defined for Sim +template +using get_migration_factors_expr_t = decltype(get_migration_factors( + std::declval(), std::declval(), std::declval&>())); -/** - * @brief Sets the graph edges. - * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. - * @param[in] travel_times_path Path to txt file containing the travel times between counties. - * @param[in] mobility_data_path Path to txt file containing the commuting matrices. - * @param[in] travelpath_path Path to txt file containing the paths between counties. - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] migrating_compartments Compartments that commute. - * @param[in] contact_locations_size Number of contact locations. - * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. - */ -template -IOResult -set_edges_detailed(const std::string& travel_times_path, const std::string mobility_data_path, - const std::string& travelpath_path, GraphDetailed& params_graph, - std::initializer_list& migrating_compartments, size_t contact_locations_size, - std::vector commuting_weights = std::vector{}, - ScalarType theshold_edges = 4e-5) +// Default implementation when get_migration_factors is not defined for Sim +template ::value, void*> = nullptr> +auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref& y) { - BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_path)); - BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_path)); - BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travelpath_path)); - - for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { - for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { - auto& populations = params_graph.nodes()[county_idx_i].property.populations; - - // mobility coefficients have the same number of components as the contact matrices. - // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) - auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); - auto num_age_groups = (size_t)params_graph.nodes()[county_idx_i].property.parameters.get_num_groups(); - commuting_weights = - (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); - - //commuters - auto working_population = 0.0; - auto min_commuter_age = mio::AgeGroup(2); - auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes - for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { - working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; - } - auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; - for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { - for (auto compartment : migrating_compartments) { - auto coeff_index = populations.get_flat_index({age, compartment}); - mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = - commuter_coeff_ij * commuting_weights[size_t(age)]; - } - } - - auto path = path_mobility[county_idx_i][county_idx_j]; - if (static_cast(path[0]) != county_idx_i || - static_cast(path[path.size() - 1]) != county_idx_j) - std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; - - //only add edges with mobility above thresholds for performance - if (commuter_coeff_ij > theshold_edges) { - params_graph.add_edge(county_idx_i, county_idx_j, travel_times(county_idx_i, county_idx_j), - path_mobility[county_idx_i][county_idx_j], std::move(mobility_coeffs)); - } - } - } - - return success(); + return Eigen::VectorXd::Ones(y.rows()); } -template -class MigrationEdgeDetailed : public MigrationEdge -{ -public: - using MigrationEdge::MigrationEdge; - - template - void apply_migration(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to, int mode) - { - - if (mode == 0) { - //normal daily migration - this->m_migrated.add_time_point(t, (node_from.get_last_state().array() * - this->m_parameters.get_coefficients().get_matrix_at(t).array() * - get_migration_factors(node_from, t, node_from.get_last_state()).array()) - .matrix()); - this->m_return_times.add_time_point(t + dt); - move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), - node_to.get_result().get_last_value()); - } - // change county of migrated - else if (mode == 1) { - // update status of migrated before moving to next county - auto& integrator_node = node_from.get_simulation().get_integrator(); - update_status_migrated(this->m_migrated.get_last_value(), node_from.get_simulation(), integrator_node, - node_from.get_result().get_last_value(), t, dt); - move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), - node_to.get_result().get_last_value()); - } - // option for last time point to remove time points - else if (mode == 2) { - Eigen::Index idx = this->m_return_times.get_num_time_points() - 1; - auto& integrator_node = node_from.get_simulation().get_integrator(); - update_status_migrated(this->m_migrated[idx], node_from.get_simulation(), integrator_node, - node_from.get_result().get_last_value(), t, dt); - - move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), - node_to.get_result().get_last_value()); - - for (Eigen::Index i = this->m_return_times.get_num_time_points() - 1; i >= 0; --i) { - if (this->m_return_times.get_time(i) <= t) { - this->m_migrated.remove_time_point(i); - this->m_return_times.remove_time_point(i); - } - } - } - // just update status of migrated - else if (mode == 3) { - Eigen::Index idx = this->m_return_times.get_num_time_points() - 1; - auto& integrator_node = node_from.get_simulation().get_integrator(); - update_status_migrated(this->m_migrated[idx], node_from.get_simulation(), integrator_node, - node_from.get_result().get_last_value(), t, dt); - - move_migrated(this->m_migrated.get_last_value(), node_from.get_result().get_last_value(), - node_from.get_result().get_last_value()); - } - else { - std::cout << "Invalid input mode. Should be 0 or 1." - << "\n"; - } - } -}; - -/** - * edge functor for migration simulation. - * @see MigrationEdge::apply_migration - */ -template -void apply_migration(FP t, FP dt, MigrationEdgeDetailed& migrationEdgeDetailed, SimulationNode& node_from, - SimulationNode& node_to, int mode) +// Use the user-defined get_migration_factors if it is defined for Sim +template ::value, void*> = nullptr> +auto get_migration_factors(const Sim& sim, double t, const Eigen::Ref& y) { - migrationEdgeDetailed.apply_migration(t, dt, node_from, node_to, mode); + return get_migration_factors(sim, t, y); } -template -class ParameterStudyDetailed : public ParameterStudy -{ -public: - /** - * The type of simulation of a single node of the graph. - */ - using Simulation = S; - /** - * The Graph type that stores the parametes of the simulation. - * This is the input of ParameterStudies. - */ - using ParametersGraphDetailed = mio::GraphDetailed>; - /** - * The Graph type that stores simulations and their results of each run. - * This is the output of ParameterStudies for each run. - */ - using SimulationGraphDetailed = mio::GraphDetailed, mio::MigrationEdge>; - - /** - * create study for graph of compartment models. - * @param graph graph of parameters - * @param t0 start time of simulations - * @param tmax end time of simulations - * @param graph_sim_dt time step of graph simulation - * @param num_runs number of runs - */ - ParameterStudyDetailed(const ParametersGraphDetailed& graph, double t0, double tmax, double graph_sim_dt, - size_t num_runs) - : ParameterStudy(graph, t0, tmax, graph_sim_dt, num_runs) - , m_graph(graph) - , m_num_runs(num_runs) - , m_t0{t0} - , m_tmax{tmax} - , m_dt_graph_sim(graph_sim_dt) - { - } - - /* - * @brief Carry out all simulations in the parameter study. - * Save memory and enable more runs by immediately processing and/or discarding the result. - * The result processing function is called when a run is finished. It receives the result of the run - * (a SimulationGraphDetailed object) and an ordered index. The values returned by the result processing function - * are gathered and returned as a list. - * This function is parallelized if memilio is configured with MEMILIO_ENABLE_MPI. - * The MPI processes each contribute a share of the runs. The sample function and result processing function - * are called in the same process that performs the run. The results returned by the result processing function are - * gathered at the root process and returned as a list by the root in the same order as if the programm - * were running sequentially. Processes other than the root return an empty list. - * @param sample_graph Function that receives the ParametersGraph and returns a sampled copy. - * @param result_processing_function Processing function for simulation results, e.g., output function. - * @returns At the root process, a list of values per run that have been returned from the result processing function. - * At all other processes, an empty list. - * @tparam SampleGraphFunction Callable type, accepts instance of ParametersGraph. - * @tparam HandleSimulationResultFunction Callable type, accepts instance of SimulationGraphDetailed and an index of type size_t. - */ - template - std::vector> - run(SampleGraphFunction sample_graph, HandleSimulationResultFunction result_processing_function) - { - int num_procs, rank; -#ifdef MEMILIO_ENABLE_MPI - MPI_Comm_size(mpi::get_world(), &num_procs); - MPI_Comm_rank(mpi::get_world(), &rank); -#else - num_procs = 1; - rank = 0; -#endif - - //The ParameterDistributions used for sampling parameters use thread_local_rng() - //So we set our own RNG to be used. - //Assume that sampling uses the thread_local_rng() and isn't multithreaded - this->m_rng.synchronize(); - thread_local_rng() = this->m_rng; - - auto run_distribution = this->distribute_runs(m_num_runs, num_procs); - auto start_run_idx = - std::accumulate(run_distribution.begin(), run_distribution.begin() + size_t(rank), size_t(0)); - auto end_run_idx = start_run_idx + run_distribution[size_t(rank)]; - - std::vector> - ensemble_result; - ensemble_result.reserve(m_num_runs); - - for (size_t run_idx = start_run_idx; run_idx < end_run_idx; run_idx++) { - log(LogLevel::info, "ParameterStudies: run {}", run_idx); - - //prepare rng for this run by setting the counter to the right offset - //Add the old counter so that this call of run() produces different results - //from the previous call - auto run_rng_counter = - this->m_rng.get_counter() + - rng_totalsequence_counter(static_cast(run_idx), Counter(0)); - thread_local_rng().set_counter(run_rng_counter); - - //sample - auto sim = create_sampled_sim(sample_graph); - log(LogLevel::info, "ParameterStudies: Generated {} random numbers.", - (thread_local_rng().get_counter() - run_rng_counter).get()); - - //perform run - sim.advance(m_tmax); - - //handle result and store - ensemble_result.emplace_back(result_processing_function(std::move(sim).get_graph(), run_idx)); - } - - //Set the counter of our RNG so that future calls of run() produce different parameters. - this->m_rng.set_counter(this->m_rng.get_counter() + - rng_totalsequence_counter(m_num_runs, Counter(0))); - -#ifdef MEMILIO_ENABLE_MPI - //gather results - if (rank == 0) { - for (int src_rank = 1; src_rank < num_procs; ++src_rank) { - int bytes_size; - MPI_Recv(&bytes_size, 1, MPI_INT, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); - ByteStream bytes(bytes_size); - MPI_Recv(bytes.data(), bytes.data_size(), MPI_BYTE, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); - - auto src_ensemble_results = deserialize_binary(bytes, Tag{}); - if (!src_ensemble_results) { - log_error("Error receiving ensemble results from rank {}.", src_rank); - } - std::copy(src_ensemble_results.value().begin(), src_ensemble_results.value().end(), - std::back_inserter(ensemble_result)); - } - } - else { - auto bytes = serialize_binary(ensemble_result); - auto bytes_size = int(bytes.data_size()); - MPI_Send(&bytes_size, 1, MPI_INT, 0, 0, mpi::get_world()); - MPI_Send(bytes.data(), bytes.data_size(), MPI_BYTE, 0, 0, mpi::get_world()); - ensemble_result.clear(); //only return root process - } -#endif - - return ensemble_result; - } - -private: - //sample parameters and create simulation - template - mio::GraphSimulationDetailed create_sampled_sim(SampleGraphFunction sample_graph) - { - SimulationGraphDetailed sim_graph; - - auto sampled_graph = sample_graph(m_graph); - for (auto&& node : sampled_graph.nodes()) { - sim_graph.add_node(node.id, node.stay_duration, node.property, node.mobility, m_t0, this->m_dt_integration); - } - for (auto&& edge : sampled_graph.edges()) { - sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.traveltime, edge.property); - } - return make_migration_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); - } - -private: - // Stores Graph with the names and ranges of all parameters - ParametersGraphDetailed m_graph; - size_t m_num_runs; - double m_t0; - double m_tmax; - double m_dt_graph_sim; -}; - -/** - * @brief number of migrated people when they return according to the model. - * E.g. during the time in the other node, some people who left as susceptible will return exposed. - * Implemented for general compartmentmodel simulations, overload for your custom model if necessary - * so that it can be found with argument-dependent lookup, i.e. in the same namespace as the model. - * @param migrated number of people that migrated as input, number of people that return as output - * @param sim Simulation that is used for the migration - * @param integrator Integrator that is used for the estimation. Has to be a one-step scheme. - * @param total total population in the node that the people migrated to. - * @param t time of migration - * @param dt timestep - */ -// template ::value>> -// void calculate_migration_returns(Eigen::Ref::Vector> migrated, const Sim& sim, -// Eigen::Ref::Vector> total, FP t, FP dt) -// { -// auto y0 = migrated.eval(); -// auto y1 = migrated; -// EulerIntegratorCore().step( -// [&](auto&& y, auto&& t_, auto&& dydt) { -// sim.get_model().get_derivatives(total, y, t_, dydt); -// }, -// y0, t, dt, y1); -// auto flows_model = sim.get_model().get_flow_values(); -// flows_model *= dt; -// sim.get_model().set_flow_values(flows_model); -// } - -template -using Vector = Eigen::Matrix; - template void move_migrated(Eigen::Ref> migrated, Eigen::Ref> results_from, Eigen::Ref> results_to) @@ -596,7 +127,6 @@ void move_migrated(Eigen::Ref> migrated, Eigen::Ref> resul for (Eigen::Index j = 0; j < migrated.size(); ++j) { if (migrated(j) < -1e-8) { std::cout << "Negative Value in migration detected. With value" << migrated(j) << "\n"; - auto compart = j % num_comparts; auto curr_age_group = int(j / num_comparts); auto indx_begin = curr_age_group * num_comparts; auto indx_end = (curr_age_group + 1) * num_comparts; @@ -625,7 +155,6 @@ void move_migrated(Eigen::Ref> migrated, Eigen::Ref> resul // remaining_after_return.data(), remaining_after_return.data() + remaining_after_return.size()); for (Eigen::Index j = 0; j < results_to.size(); ++j) { if (remaining_after_return(j) < -1e-8) { - auto compart = j % num_comparts; auto curr_age_group = int(j / num_comparts); auto indx_begin = curr_age_group * num_comparts; auto indx_end = (curr_age_group + 1) * num_comparts; @@ -686,34 +215,887 @@ void move_migrated(Eigen::Ref> migrated, Eigen::Ref> resul results_to += migrated; } -// template -// void MigrationEdgeDetailed::apply_migration(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to, -// int mode) +template +class MigrationModes +{ +public: + void init_mobility(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + { + edge.get_migrated().add_time_point( + t, (from_sim.get_result().get_last_value().array() * + edge.get_parameters().get_coefficients().get_matrix_at(t).array() * + get_migration_factors(from_sim, t, from_sim.get_result().get_last_value()).array()) + .matrix()); + edge.get_return_times().add_time_point(t + dt); + move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), + to_sim.get_result().get_last_value()); + } + + void update_and_move(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + { + auto& integrator_node = from_sim.get_integrator(); + update_status_migrated(edge.get_migrated().get_last_value(), from_sim, integrator_node, + from_sim.get_result().get_last_value(), t, dt); + move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), + to_sim.get_result().get_last_value()); + } + + void update_only(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim) + { + auto& integrator_node = from_sim.get_integrator(); + update_status_migrated(edge.get_migrated().get_last_value(), from_sim, integrator_node, + from_sim.get_result().get_last_value(), t, dt); + move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), + from_sim.get_result().get_last_value()); + } + + void move_and_delete(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + { + Eigen::Index idx = edge.get_return_times().get_num_time_points() - 1; + auto& integrator_node = from_sim.get_integrator(); + update_status_migrated(edge.get_migrated()[idx], from_sim, integrator_node, + from_sim.get_result().get_last_value(), t, dt); + + move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), + to_sim.get_result().get_last_value()); + + for (Eigen::Index i = edge.get_return_times().get_num_time_points() - 1; i >= 0; --i) { + if (edge.get_return_times().get_time(i) <= t) { + edge.get_migrated().remove_time_point(i); + edge.get_return_times().remove_time_point(i); + } + } + } +}; + +template +class GraphSimulationExtended : public GraphSimulationBase +{ +public: + using node_function = std::function; + using edge_function = + std::function; + + GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, MigrationModes modes) + : GraphSimulationBase(t0, dt, g, node_func, {}) + , m_modes(modes) + { + } + + GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MigrationModes modes) + : GraphSimulationBase(t0, dt, std::move(g), node_func, {}) + , m_modes(modes) + { + } + + inline double round_second_decimal(double x) + { + return std::round(x * 100.0) / 100.0; + } + + void advance(double t_max = 1.0) + { + ScalarType dt_first_mobility = + std::accumulate(this->m_graph.edges().begin(), this->m_graph.edges().end(), + std::numeric_limits::max(), [&](ScalarType current_min, const auto& e) { + auto traveltime_per_region = + round_second_decimal(e.property.travel_time / e.property.path.size()); + if (traveltime_per_region < 0.01) + traveltime_per_region = 0.01; + auto start_mobility = + round_second_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - + this->m_graph.nodes()[e.end_node_idx].property.stay_duration); + if (start_mobility < 0) { + start_mobility = 0.; + } + return std::min(current_min, start_mobility); + }); + + // set population to zero in mobility nodes before starting + for (auto& n : this->m_graph.nodes()) { + n.property.mobility_sim.get_result().get_last_value().setZero(); + } + + auto min_dt = 0.01; + double t_begin = this->m_t - 1.; + + // calc schedule for each edge + precompute_schedule(); + while (this->m_t - epsilon < t_max) { + // auto start = std::chrono::system_clock::now(); + + t_begin += 1; + if (this->m_t + dt_first_mobility > t_max) { + dt_first_mobility = t_max - this->m_t; + for (auto& n : this->m_graph.nodes()) { + // m_node_func(this->m_t, dt_first_mobility, n.property); + n.property.base_sim.advance(this->m_t + dt_first_mobility); + } + break; + } + + for (auto& node : this->m_graph.nodes()) { + node.property.mobility_sim.set_integrator(std::make_shared>()); + } + + size_t indx_schedule = 0; + while (t_begin + 1 > this->m_t + 1e-10) { + advance_edges(indx_schedule); + + // first we integrate the nodes in time. Afterwards the update on the edges is done. + // We start with the edges since the values for t0 are given. + advance_local_nodes(indx_schedule); + advance_mobility_nodes(indx_schedule); + + indx_schedule++; + this->m_t += min_dt; + } + // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate + // the state of the indivuals moving between different counties. + // Therefore there can be differences with the states given by the integrator used for the mobility node. + for (auto& n : this->m_graph.nodes()) { + n.property.mobility_sim.get_result().get_last_value().setZero(); + } + } + } + +private: + MigrationModes m_modes; + + // describes the schedule for each edge, i.e. which node is visited at which time step + std::vector> schedule_edges; + + // defines if people from a edge are currently in a mobility node. This is necessary to determine the correct + // position in the schedule. Otherwise we could add a specific identifier in schedule_edges. + std::vector> mobility_schedule_edges; + + // describes the syncronization steps which are necessary for the integrator in the edges and nodes. + std::vector> mb_int_schedule; + std::vector> ln_int_schedule; + + // defines the edges on which mobility takes place for each time step + std::vector> edges_mobility; + + // same for the nodes + std::vector> nodes_mobility; + std::vector> nodes_mobility_m; + + // first mobility activites per edge + std::vector first_mobility; + + const double epsilon = 1e-10; + + void precompute_schedule() + { + const size_t timesteps = 100; + schedule_edges.reserve(this->m_graph.edges().size()); + mobility_schedule_edges.reserve(this->m_graph.edges().size()); + + // calculate the schedule for each edge + for (auto& e : this->m_graph.edges()) { + // Initialize the schedule and mobility node vectors for the edge + std::vector schedule(timesteps, 0); + std::vector is_mobility_node(timesteps, false); + + // Calculate travel time per region, ensuring a minimum value of 0.01 + const double traveltime_per_region = + std::max(0.01, round_second_decimal(e.property.travel_time / e.property.path.size())); + + // Calculate the start time for mobility, ensuring it is not negative + const double start_mobility = + std::max(0.0, round_second_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - + this->m_graph.nodes()[e.end_node_idx].property.stay_duration)); + + // Calculate the arrival time at the destination node + const double arrive_at = start_mobility + traveltime_per_region * e.property.path.size(); + + // Lambda to fill the schedule vector with a specific value over a range + auto fill_schedule = [&](size_t start_idx, size_t end_idx, size_t value) { + std::fill(schedule.begin() + start_idx, schedule.begin() + end_idx, value); + }; + + // Lambda to fill the mobility node vector with a boolean value over a range + auto fill_mobility = [&](size_t start_idx, size_t end_idx, bool value) { + std::fill(is_mobility_node.begin() + start_idx, is_mobility_node.begin() + end_idx, value); + }; + + // Indices for schedule filling + size_t start_idx = static_cast((start_mobility + epsilon) * 100); + size_t arrive_idx = static_cast((arrive_at + epsilon) * 100); + size_t stay_end_idx = timesteps - (arrive_idx - start_idx); + + // Fill the schedule up to the start of mobility with the start node index + fill_schedule(0, start_idx, e.start_node_idx); + + // Mark the mobility period in the mobility node vector + fill_mobility(start_idx, arrive_idx, true); + + // Fill the schedule for the path during the mobility period + size_t current_index = start_idx; + for (size_t county : e.property.path) { + size_t next_index = current_index + static_cast((traveltime_per_region + epsilon) * 100); + fill_schedule(current_index, next_index, county); + current_index = next_index; + } + + // Fill the remaining schedule after mobility with the end node index + fill_schedule(current_index, stay_end_idx, e.property.path.back()); + + // Mark the return mobility period in the mobility node vector + fill_mobility(stay_end_idx, timesteps, true); + + // Find the first and last true values in the mobility node vector + auto first_true = std::find(is_mobility_node.begin(), is_mobility_node.end(), true); + auto last_true = std::find(is_mobility_node.rbegin(), is_mobility_node.rend(), true); + + // Ensure there is at least one true value + if (first_true != is_mobility_node.end() && last_true != is_mobility_node.rend()) { + size_t first_index = std::distance(is_mobility_node.begin(), first_true); + size_t count_true = std::count(is_mobility_node.begin(), is_mobility_node.end(), true); + + // Create a reversed path segment for the return trip + std::vector path_reversed(schedule.begin() + first_index, + schedule.begin() + first_index + count_true); + std::reverse(path_reversed.begin(), path_reversed.end()); + + // Copy the reversed path segment to the end of the schedule for the return trip + std::copy(path_reversed.begin(), path_reversed.end(), schedule.end() - count_true); + + // Add the schedule and mobility node vectors to their respective containers + schedule_edges.push_back(std::move(schedule)); + mobility_schedule_edges.push_back(std::move(is_mobility_node)); + } + else { + log_error("Error in creating schedule."); + } + } + + // iterate over nodes to create the integration schedule for each node. The integration schedule is necessary + // to determine the correct time step for the integrator in the nodes. + for (size_t indx_node = 0; indx_node < this->m_graph.nodes().size(); ++indx_node) { + // Local node initialization with starting at t=0 + std::vector order_local_node = {0}; + std::vector indx_edges; + + // Find edges starting from the current node and not in mobility at t=0 + auto find_edges = [&](size_t t, bool mobility) { + std::vector edges; + for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { + if (schedule_edges[indx_edge][t] == indx_node && + mobility_schedule_edges[indx_edge][t] == mobility) { + edges.push_back(indx_edge); + } + } + return edges; + }; + + indx_edges = find_edges(0, false); + + // Iterate through each timestep to identify changes in local node schedule + for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { + auto indx_edges_new = find_edges(indx_t, false); + + if (indx_edges_new.size() != indx_edges.size() || + !std::equal(indx_edges.begin(), indx_edges.end(), indx_edges_new.begin())) { + order_local_node.push_back(indx_t); + indx_edges = indx_edges_new; + } + } + + // Ensure the last timestep is included + if (order_local_node.back() != timesteps - 1) { + order_local_node.push_back(timesteps - 1); + } + ln_int_schedule.push_back(order_local_node); + + // Mobility node initialization + std::vector order_mobility_node; + std::vector indx_edges_mobility; + + indx_edges_mobility = find_edges(0, true); + + // Iterate through each timestep to identify changes in mobility node schedule + for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { + auto indx_edges_mobility_new = find_edges(indx_t, true); + + if (indx_edges_mobility_new.size() != indx_edges_mobility.size() || + !std::equal(indx_edges_mobility.begin(), indx_edges_mobility.end(), + indx_edges_mobility_new.begin())) { + order_mobility_node.push_back(indx_t); + indx_edges_mobility = indx_edges_mobility_new; + } + } + + mb_int_schedule.push_back(order_mobility_node); + } + + // Reserve space for first_mobility vector and initialize it + first_mobility.reserve(this->m_graph.edges().size()); + for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { + first_mobility[indx_edge] = std::distance( + mobility_schedule_edges[indx_edge].begin(), + std::find(mobility_schedule_edges[indx_edge].begin(), mobility_schedule_edges[indx_edge].end(), true)); + } + + // Reserve space for mobility-related vectors + edges_mobility.reserve(timesteps); + nodes_mobility.reserve(timesteps); + nodes_mobility_m.reserve(timesteps); + + // Handle the case where indx_current = 0 separately + std::vector temp_edges_mobility; + for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { + if (first_mobility[indx_edge] == 0) { + temp_edges_mobility.push_back(indx_edge); + } + } + edges_mobility.push_back(std::move(temp_edges_mobility)); + + // Initialize nodes_mobility with all node indices for t=0 + std::vector temp_nodes_mobility(this->m_graph.nodes().size()); + std::iota(temp_nodes_mobility.begin(), temp_nodes_mobility.end(), 0); + nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); + + // Initialize nodes_mobility_m with nodes that have mobility activities at t=0 + std::vector temp_nodes_mobility_m; + for (size_t node_indx = 0; node_indx < this->m_graph.nodes().size(); ++node_indx) { + if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), 0)) { + temp_nodes_mobility_m.push_back(node_indx); + } + } + nodes_mobility_m.push_back(temp_nodes_mobility_m); + + for (size_t indx_current = 1; indx_current < timesteps; ++indx_current) { + std::vector temp_edge_mobility; + for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { + size_t current_node_indx = schedule_edges[indx_edge][indx_current]; + if (indx_current >= first_mobility[indx_edge]) { + if (mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(mb_int_schedule[current_node_indx].begin(), + mb_int_schedule[current_node_indx].end(), indx_current)) { + temp_edge_mobility.push_back(indx_edge); + } + else if (!mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(ln_int_schedule[current_node_indx].begin(), + ln_int_schedule[current_node_indx].end(), indx_current)) { + temp_edge_mobility.push_back(indx_edge); + } + } + } + edges_mobility.push_back(temp_edge_mobility); + + // Clear and fill temp_nodes_mobility and temp_nodes_mobility_m for current timestep + temp_nodes_mobility.clear(); + temp_nodes_mobility_m.clear(); + for (size_t node_indx = 0; node_indx < this->m_graph.nodes().size(); ++node_indx) { + if (std::binary_search(ln_int_schedule[node_indx].begin(), ln_int_schedule[node_indx].end(), + indx_current)) { + temp_nodes_mobility.push_back(node_indx); + } + + if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), + indx_current)) { + temp_nodes_mobility_m.push_back(node_indx); + } + } + nodes_mobility.push_back(temp_nodes_mobility); + nodes_mobility_m.push_back(temp_nodes_mobility_m); + } + } + + void advance_edges(size_t indx_schedule) + { + for (const auto& edge_indx : edges_mobility[indx_schedule]) { + auto& e = this->m_graph.edges()[edge_indx]; + // first mobility activity + if (indx_schedule == first_mobility[edge_indx]) { + auto& node_from = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; + auto& node_to = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; + // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); + m_modes.init_mobility(this->m_t, 0.0, e.property, node_from, node_to); + } + // next mobility activity + else if (indx_schedule > first_mobility[edge_indx]) { + auto current_node_indx = schedule_edges[edge_indx][indx_schedule]; + bool in_mobility_node = mobility_schedule_edges[edge_indx][indx_schedule]; + + // determine dt, which is equal to the last integration/syncronization point in the current node + auto integrator_schedule_row = ln_int_schedule[current_node_indx]; + if (in_mobility_node) + integrator_schedule_row = mb_int_schedule[current_node_indx]; + // search the index of indx_schedule in the integrator schedule + const size_t indx_current = std::distance( + integrator_schedule_row.begin(), + std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); + + if (integrator_schedule_row[indx_current] != indx_schedule) + std::cout << "Error in schedule." + << "\n"; + + ScalarType dt_mobility; + if (indx_current == 0) { + dt_mobility = round_second_decimal(e.property.travel_time / e.property.path.size()); + if (dt_mobility < 0.01) + dt_mobility = 0.01; + } + else { + dt_mobility = + round_second_decimal((static_cast(integrator_schedule_row[indx_current]) - + static_cast(integrator_schedule_row[indx_current - 1])) / + 100 + + epsilon); + } + + // We have two cases. Either, we want to send the individuals to the next node, or we just want + // to update their state since a syncronization step is necessary in the current node. + if ((schedule_edges[edge_indx][indx_schedule] != schedule_edges[edge_indx][indx_schedule - 1]) || + (mobility_schedule_edges[edge_indx][indx_schedule] != + mobility_schedule_edges[edge_indx][indx_schedule - 1])) { + auto& node_from = + mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.mobility_sim + : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; + + auto& node_to = + mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim + : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.base_sim; + + if (indx_schedule < mobility_schedule_edges[edge_indx].size() - 1) { + // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); + m_modes.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); + } + else { + // the last time step is handled differently since we have to close the timeseries + // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 2); + m_modes.move_and_delete(this->m_t, dt_mobility, e.property, node_from, node_to); + } + } + else { + auto& node_from = + mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.mobility_sim + : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; + + auto& node_to = + mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim + : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.base_sim; + + assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); + // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); + m_modes.update_only(this->m_t, dt_mobility, e.property, node_from); + } + } + } + } + + void advance_local_nodes(size_t indx_schedule) + { + for (const auto& n_indx : nodes_mobility[indx_schedule]) { + auto& n = this->m_graph.nodes()[n_indx]; + const size_t indx_current = std::distance( + ln_int_schedule[n_indx].begin(), + std::lower_bound(ln_int_schedule[n_indx].begin(), ln_int_schedule[n_indx].end(), indx_schedule)); + + const size_t val_next = + (indx_current == ln_int_schedule[n_indx].size() - 1) ? 100 : ln_int_schedule[n_indx][indx_current + 1]; + const ScalarType next_dt = + round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + n.property.base_sim.advance(this->m_t + next_dt); + // m_node_func(this->m_t, next_dt, n.property.base_sim); + } + } + + void advance_mobility_nodes(size_t indx_schedule) + { + for (const size_t& n_indx : nodes_mobility_m[indx_schedule]) { + auto& n = this->m_graph.nodes()[n_indx]; + const size_t indx_current = std::distance( + mb_int_schedule[n_indx].begin(), + std::lower_bound(mb_int_schedule[n_indx].begin(), mb_int_schedule[n_indx].end(), indx_schedule)); + const size_t val_next = + (indx_current == mb_int_schedule[n_indx].size() - 1) ? 100 : mb_int_schedule[n_indx][indx_current + 1]; + const ScalarType next_dt = + round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + + // get all time points from the last integration step + auto& last_time_point = n.property.mobility_sim.get_result().get_time( + n.property.mobility_sim.get_result().get_num_time_points() - 1); + // wenn last_time_point nicht innerhalb eines intervalls von 1-e10 von t liegt, dann setzte den letzten Zeitpunkt auf m_t + if (std::fabs(last_time_point - this->m_t) > 1e-10) { + n.property.mobility_sim.get_result().get_last_time() = this->m_t; + } + // m_node_func(this->m_t, next_dt, n.property.mobility_sim); + n.property.mobility_sim.advance(this->m_t + next_dt); + Eigen::Index indx_min; + while (n.property.mobility_sim.get_result().get_last_value().minCoeff(&indx_min) < -1e-7) { + Eigen::Index indx_max; + n.property.mobility_sim.get_result().get_last_value().maxCoeff(&indx_max); + n.property.mobility_sim.get_result().get_last_value()[indx_max] -= + n.property.mobility_sim.get_result().get_last_value()[indx_min]; + n.property.mobility_sim.get_result().get_last_value()[indx_min] = 0; + } + } + } +}; -/** - * create a migration simulation. - * After every second time step, for each edge a portion of the population corresponding to the coefficients of the edge - * moves from one node to the other. In the next timestep, the migrated population return to their "home" node. - * Returns are adjusted based on the development in the target node. - * @param t0 start time of the simulation - * @param dt time step between migrations - * @param graph set up for migration simulation - * @{ - */ template -GraphSimulationDetailed, MigrationEdge>> -make_migration_sim(double t0, double dt, const GraphDetailed, MigrationEdge>& graph) +GraphSimulationExtended, ExtendedMigrationEdge>, MigrationModes> +make_extended_migration_sim(FP t0, FP dt, Graph, ExtendedMigrationEdge>&& graph) { - return make_graph_sim_detailed(t0, dt, graph, &evolve_model, &apply_migration); + auto migration_modes = MigrationModes(); + return GraphSimulationExtended, ExtendedMigrationEdge>, + MigrationModes>(t0, dt, std::move(graph), {}, migration_modes); } -template -GraphSimulationDetailed, MigrationEdge>> -make_migration_sim(double t0, double dt, GraphDetailed, MigrationEdge>&& graph) +/** + * @brief number of migrated people when they return according to the model. + * E.g. during the time in the other node, some people who left as susceptible will return exposed. + * Implemented for general compartmentmodel simulations, overload for your custom model if necessary + * so that it can be found with argument-dependent lookup, i.e. in the same namespace as the model. + * @param migrated number of people that migrated as input, number of people that return as output + * @param sim Simulation that is used for the migration + * @param integrator Integrator that is used for the estimation. Has to be a one-step scheme. + * @param total total population in the node that the people migrated to. + * @param t time of migration + * @param dt timestep + */ + +template ::value>> +void update_status_migrated(Eigen::Ref::Vector> migrated, Sim& sim, + mio::IntegratorCore& integrator, + Eigen::Ref::Vector> total, FP t, FP dt) { - return make_graph_sim_detailed(t0, dt, std::move(graph), &evolve_model, &apply_migration); + auto y0 = migrated.eval(); + auto y1 = migrated; + integrator.step( + [&](auto&& y, auto&& t_, auto&& dydt) { + sim.get_model().get_derivatives(total, y, t_, dydt); + }, + y0, t, dt, y1); + auto flows_model = sim.get_model().get_flow_values(); + flows_model *= dt; + sim.get_model().set_flow_values(flows_model); } +// /** +// * @brief Sets the graph nodes for counties or districts. +// * Reads the node ids which could refer to districts or counties and the epidemiological +// * data from json files and creates one node for each id. Every node contains a model. +// * @param[in] params Model Parameters that are used for every node. +// * @param[in] start_date Start date for which the data should be read. +// * @param[in] end_data End date for which the data should be read. +// * @param[in] data_dir Directory that contains the data files. +// * @param[in] population_data_path Path to json file containing the population data. +// * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. +// * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). +// * @param[in, out] params_graph Graph whose nodes are set by the function. +// * @param[in] read_func Function that reads input data for german counties and sets Model compartments. +// * @param[in] node_func Function that returns the county ids. +// * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. +// * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. +// * @param[in] tnt_capacity_factor Factor for test and trace capacity. +// * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. +// * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. +// */ +// template +// IOResult set_nodes_detailed(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, +// const std::string& population_data_path, const std::string& stay_times_data_path, +// bool is_node_for_county, GraphDetailed& params_graph, +// ReadFunction&& read_func, NodeIdFunction&& node_func, +// const std::vector& scaling_factor_inf, double scaling_factor_icu, +// double tnt_capacity_factor, int num_days = 0, bool export_time_series = false) +// { + +// BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); +// BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county)); + +// std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); + +// for (auto& node : nodes) { +// node.parameters = params; +// } +// BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), +// num_days, export_time_series)); + +// for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { + +// auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; + +// //local parameters +// auto& tnt_value = nodes[node_idx].parameters.template get(); +// tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); +// tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); + +// //holiday periods +// auto id = int(mio::regions::CountyId(node_ids[node_idx])); +// auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); +// auto& contacts = nodes[node_idx].parameters.template get(); +// contacts.get_school_holidays() = +// std::vector>(holiday_periods.size()); +// std::transform( +// holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { +// return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), +// mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); +// }); + +// //uncertainty in populations +// for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { +// for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { +// auto& compartment_value = nodes[node_idx].populations[{i, j}]; +// compartment_value = +// mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); +// compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), +// 1.1 * double(compartment_value))); +// } +// } + +// // Add mobility node +// auto mobility = nodes[node_idx]; +// mobility.populations.set_total(0); + +// params_graph.add_node(node_ids[node_idx], duration_stay((Eigen::Index)node_idx), nodes[node_idx], mobility); +// } +// return success(); +// } + +// /** +// * @brief Sets the graph edges. +// * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. +// * @param[in] travel_times_path Path to txt file containing the travel times between counties. +// * @param[in] mobility_data_path Path to txt file containing the commuting matrices. +// * @param[in] travelpath_path Path to txt file containing the paths between counties. +// * @param[in, out] params_graph Graph whose nodes are set by the function. +// * @param[in] migrating_compartments Compartments that commute. +// * @param[in] contact_locations_size Number of contact locations. +// * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. +// */ +// template +// IOResult +// set_edges_detailed(const std::string& travel_times_path, const std::string mobility_data_path, +// const std::string& travelpath_path, GraphDetailed& params_graph, +// std::initializer_list& migrating_compartments, size_t contact_locations_size, +// std::vector commuting_weights = std::vector{}, +// ScalarType theshold_edges = 4e-5) +// { +// BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_path)); +// BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_path)); +// BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travelpath_path)); + +// for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { +// for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { +// auto& populations = params_graph.nodes()[county_idx_i].property.populations; + +// // mobility coefficients have the same number of components as the contact matrices. +// // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) +// auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); +// auto num_age_groups = (size_t)params_graph.nodes()[county_idx_i].property.parameters.get_num_groups(); +// commuting_weights = +// (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); + +// //commuters +// auto working_population = 0.0; +// auto min_commuter_age = mio::AgeGroup(2); +// auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes +// for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { +// working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; +// } +// auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; +// for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { +// for (auto compartment : migrating_compartments) { +// auto coeff_index = populations.get_flat_index({age, compartment}); +// mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = +// commuter_coeff_ij * commuting_weights[size_t(age)]; +// } +// } + +// auto path = path_mobility[county_idx_i][county_idx_j]; +// if (static_cast(path[0]) != county_idx_i || +// static_cast(path[path.size() - 1]) != county_idx_j) +// std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; + +// //only add edges with mobility above thresholds for performance +// if (commuter_coeff_ij > theshold_edges) { +// params_graph.add_edge(county_idx_i, county_idx_j, travel_times(county_idx_i, county_idx_j), +// path_mobility[county_idx_i][county_idx_j], std::move(mobility_coeffs)); +// } +// } +// } + +// return success(); +// } + + +// template +// class ParameterStudyDetailed : public ParameterStudy +// { +// public: +// /** +// * The type of simulation of a single node of the graph. +// */ +// using Simulation = S; +// /** +// * The Graph type that stores the parametes of the simulation. +// * This is the input of ParameterStudies. +// */ +// using ParametersGraphDetailed = mio::GraphDetailed>; +// /** +// * The Graph type that stores simulations and their results of each run. +// * This is the output of ParameterStudies for each run. +// */ +// using SimulationGraphDetailed = mio::GraphDetailed, mio::MigrationEdge>; + +// /** +// * create study for graph of compartment models. +// * @param graph graph of parameters +// * @param t0 start time of simulations +// * @param tmax end time of simulations +// * @param graph_sim_dt time step of graph simulation +// * @param num_runs number of runs +// */ +// ParameterStudyDetailed(const ParametersGraphDetailed& graph, double t0, double tmax, double graph_sim_dt, +// size_t num_runs) +// : ParameterStudy(graph, t0, tmax, graph_sim_dt, num_runs) +// , m_graph(graph) +// , m_num_runs(num_runs) +// , m_t0{t0} +// , m_tmax{tmax} +// , m_dt_graph_sim(graph_sim_dt) +// { +// } + +// /* +// * @brief Carry out all simulations in the parameter study. +// * Save memory and enable more runs by immediately processing and/or discarding the result. +// * The result processing function is called when a run is finished. It receives the result of the run +// * (a SimulationGraphDetailed object) and an ordered index. The values returned by the result processing function +// * are gathered and returned as a list. +// * This function is parallelized if memilio is configured with MEMILIO_ENABLE_MPI. +// * The MPI processes each contribute a share of the runs. The sample function and result processing function +// * are called in the same process that performs the run. The results returned by the result processing function are +// * gathered at the root process and returned as a list by the root in the same order as if the programm +// * were running sequentially. Processes other than the root return an empty list. +// * @param sample_graph Function that receives the ParametersGraph and returns a sampled copy. +// * @param result_processing_function Processing function for simulation results, e.g., output function. +// * @returns At the root process, a list of values per run that have been returned from the result processing function. +// * At all other processes, an empty list. +// * @tparam SampleGraphFunction Callable type, accepts instance of ParametersGraph. +// * @tparam HandleSimulationResultFunction Callable type, accepts instance of SimulationGraphDetailed and an index of type size_t. +// */ +// template +// std::vector> +// run(SampleGraphFunction sample_graph, HandleSimulationResultFunction result_processing_function) +// { +// int num_procs, rank; +// #ifdef MEMILIO_ENABLE_MPI +// MPI_Comm_size(mpi::get_world(), &num_procs); +// MPI_Comm_rank(mpi::get_world(), &rank); +// #else +// num_procs = 1; +// rank = 0; +// #endif + +// //The ParameterDistributions used for sampling parameters use thread_local_rng() +// //So we set our own RNG to be used. +// //Assume that sampling uses the thread_local_rng() and isn't multithreaded +// this->m_rng.synchronize(); +// thread_local_rng() = this->m_rng; + +// auto run_distribution = this->distribute_runs(m_num_runs, num_procs); +// auto start_run_idx = +// std::accumulate(run_distribution.begin(), run_distribution.begin() + size_t(rank), size_t(0)); +// auto end_run_idx = start_run_idx + run_distribution[size_t(rank)]; + +// std::vector> +// ensemble_result; +// ensemble_result.reserve(m_num_runs); + +// for (size_t run_idx = start_run_idx; run_idx < end_run_idx; run_idx++) { +// log(LogLevel::info, "ParameterStudies: run {}", run_idx); + +// //prepare rng for this run by setting the counter to the right offset +// //Add the old counter so that this call of run() produces different results +// //from the previous call +// auto run_rng_counter = +// this->m_rng.get_counter() + +// rng_totalsequence_counter(static_cast(run_idx), Counter(0)); +// thread_local_rng().set_counter(run_rng_counter); + +// //sample +// auto sim = create_sampled_sim(sample_graph); +// log(LogLevel::info, "ParameterStudies: Generated {} random numbers.", +// (thread_local_rng().get_counter() - run_rng_counter).get()); + +// //perform run +// sim.advance(m_tmax); + +// //handle result and store +// ensemble_result.emplace_back(result_processing_function(std::move(sim).get_graph(), run_idx)); +// } + +// //Set the counter of our RNG so that future calls of run() produce different parameters. +// this->m_rng.set_counter(this->m_rng.get_counter() + +// rng_totalsequence_counter(m_num_runs, Counter(0))); + +// #ifdef MEMILIO_ENABLE_MPI +// //gather results +// if (rank == 0) { +// for (int src_rank = 1; src_rank < num_procs; ++src_rank) { +// int bytes_size; +// MPI_Recv(&bytes_size, 1, MPI_INT, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); +// ByteStream bytes(bytes_size); +// MPI_Recv(bytes.data(), bytes.data_size(), MPI_BYTE, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); + +// auto src_ensemble_results = deserialize_binary(bytes, Tag{}); +// if (!src_ensemble_results) { +// log_error("Error receiving ensemble results from rank {}.", src_rank); +// } +// std::copy(src_ensemble_results.value().begin(), src_ensemble_results.value().end(), +// std::back_inserter(ensemble_result)); +// } +// } +// else { +// auto bytes = serialize_binary(ensemble_result); +// auto bytes_size = int(bytes.data_size()); +// MPI_Send(&bytes_size, 1, MPI_INT, 0, 0, mpi::get_world()); +// MPI_Send(bytes.data(), bytes.data_size(), MPI_BYTE, 0, 0, mpi::get_world()); +// ensemble_result.clear(); //only return root process +// } +// #endif + +// return ensemble_result; +// } + +// private: +// //sample parameters and create simulation +// template +// mio::GraphSimulationDetailed create_sampled_sim(SampleGraphFunction sample_graph) +// { +// SimulationGraphDetailed sim_graph; + +// auto sampled_graph = sample_graph(m_graph); +// for (auto&& node : sampled_graph.nodes()) { +// sim_graph.add_node(node.id, node.stay_duration, node.property, node.mobility, m_t0, this->m_dt_integration); +// } +// for (auto&& edge : sampled_graph.edges()) { +// sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property.travel_time, edge.property); +// } +// return make_migration_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); +// } + +// private: +// // Stores Graph with the names and ranges of all parameters +// ParametersGraphDetailed m_graph; +// size_t m_num_runs; +// double m_t0; +// double m_tmax; +// double m_dt_graph_sim; +// }; + } // namespace mio #endif //METAPOPULATION_MOBILITY_DETAILED_H \ No newline at end of file From 9ae0d0e8edb1c471ffb12e969b2ec9ac49be5a1d Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Sat, 13 Jul 2024 05:20:41 +0200 Subject: [PATCH 03/26] minor naming corrections --- cpp/examples/ode_seir_detailed_mobility.cpp | 2 +- cpp/memilio/mobility/graph_simulation.h | 2 -- .../metapopulation_mobility_detailed.h | 30 +++++++++---------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index dc2c0f4fbb..1eac79993c 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2024 MEmilio * -* Authors: Daniel Abele +* Authors: Henrik Zunker * * Contact: Martin J. Kuehn * diff --git a/cpp/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index 13ce3a0e0c..003ae08793 100644 --- a/cpp/memilio/mobility/graph_simulation.h +++ b/cpp/memilio/mobility/graph_simulation.h @@ -20,10 +20,8 @@ #ifndef MIO_MOBILITY_GRAPH_SIMULATION_H #define MIO_MOBILITY_GRAPH_SIMULATION_H -#include "memilio/math/euler.h" #include "memilio/mobility/graph.h" #include "memilio/utils/random_number_generator.h" -#include namespace mio { diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 60fc8e65ae..7acbf7fcfd 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -216,7 +216,7 @@ void move_migrated(Eigen::Ref> migrated, Eigen::Ref> resul } template -class MigrationModes +class MobilityFunctions { public: void init_mobility(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) @@ -268,24 +268,24 @@ class MigrationModes } }; -template +template class GraphSimulationExtended : public GraphSimulationBase { public: using node_function = std::function; using edge_function = std::function; + typename Graph::NodeProperty&, MobilityFunctions&)>; - GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, MigrationModes modes) + GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, MobilityFunctions modes) : GraphSimulationBase(t0, dt, g, node_func, {}) - , m_modes(modes) + , m_mobility_functions(modes) { } - GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MigrationModes modes) + GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MobilityFunctions modes) : GraphSimulationBase(t0, dt, std::move(g), node_func, {}) - , m_modes(modes) + , m_mobility_functions(modes) { } @@ -361,7 +361,7 @@ class GraphSimulationExtended : public GraphSimulationBase } private: - MigrationModes m_modes; + MobilityFunctions m_mobility_functions; // describes the schedule for each edge, i.e. which node is visited at which time step std::vector> schedule_edges; @@ -613,7 +613,7 @@ class GraphSimulationExtended : public GraphSimulationBase auto& node_from = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; auto& node_to = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); - m_modes.init_mobility(this->m_t, 0.0, e.property, node_from, node_to); + m_mobility_functions.init_mobility(this->m_t, 0.0, e.property, node_from, node_to); } // next mobility activity else if (indx_schedule > first_mobility[edge_indx]) { @@ -664,12 +664,12 @@ class GraphSimulationExtended : public GraphSimulationBase if (indx_schedule < mobility_schedule_edges[edge_indx].size() - 1) { // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); - m_modes.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); + m_mobility_functions.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); } else { // the last time step is handled differently since we have to close the timeseries // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 2); - m_modes.move_and_delete(this->m_t, dt_mobility, e.property, node_from, node_to); + m_mobility_functions.move_and_delete(this->m_t, dt_mobility, e.property, node_from, node_to); } } else { @@ -685,7 +685,7 @@ class GraphSimulationExtended : public GraphSimulationBase assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); - m_modes.update_only(this->m_t, dt_mobility, e.property, node_from); + m_mobility_functions.update_only(this->m_t, dt_mobility, e.property, node_from); } } } @@ -742,12 +742,12 @@ class GraphSimulationExtended : public GraphSimulationBase }; template -GraphSimulationExtended, ExtendedMigrationEdge>, MigrationModes> +GraphSimulationExtended, ExtendedMigrationEdge>, MobilityFunctions> make_extended_migration_sim(FP t0, FP dt, Graph, ExtendedMigrationEdge>&& graph) { - auto migration_modes = MigrationModes(); + auto migration_modes = MobilityFunctions(); return GraphSimulationExtended, ExtendedMigrationEdge>, - MigrationModes>(t0, dt, std::move(graph), {}, migration_modes); + MobilityFunctions>(t0, dt, std::move(graph), {}, migration_modes); } /** From cac4e3cdf14e959e711a9cf832e882bfe0df003c Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:41:23 +0200 Subject: [PATCH 04/26] schedule computation in own class --- cpp/examples/ode_seir_detailed_mobility.cpp | 5 +- .../metapopulation_mobility_detailed.h | 492 ++++++++++-------- 2 files changed, 285 insertions(+), 212 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index 1eac79993c..6daa8ec8d1 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -76,12 +76,13 @@ int main() sim.advance(tmax); // results node 1 std::cout << "Results node 1" << std::endl; - auto interpolated_sim1 = mio::interpolate_simulation_result(sim.get_graph().nodes()[1].property.base_sim.get_result()); + auto interpolated_sim1 = + mio::interpolate_simulation_result(sim.get_graph().nodes()[1].property.base_sim.get_result()); interpolated_sim1.print_table({"S", "E", "I", "R"}); // results node 1 mobility_sim std::cout << "Mobility results node 1" << std::endl; - auto interpolated_sim1_mobility = sim.get_graph().nodes()[1].property.mobility_sim.get_result(); + auto interpolated_sim1_mobility = sim.get_graph().nodes()[1].property.mobility_sim.get_result(); interpolated_sim1_mobility.print_table({"S", "E", "I", "R"}); return 0; } diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 7acbf7fcfd..d6c278382b 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -268,134 +268,56 @@ class MobilityFunctions } }; -template -class GraphSimulationExtended : public GraphSimulationBase +class ScheduleManager { public: - using node_function = std::function; - using edge_function = - std::function; - - GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, MobilityFunctions modes) - : GraphSimulationBase(t0, dt, g, node_func, {}) - , m_mobility_functions(modes) + struct Schedule { + std::vector> schedule_edges; + std::vector> mobility_schedule_edges; + std::vector> mobility_int_schedule; + std::vector> local_int_schedule; + std::vector> edges_mobility; + std::vector> nodes_mobility; + std::vector> nodes_mobility_m; + std::vector first_mobility; + }; + + ScheduleManager(size_t t, double eps = 1e-10) + : timesteps(t) + , epsilon(eps) { } - GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MobilityFunctions modes) - : GraphSimulationBase(t0, dt, std::move(g), node_func, {}) - , m_mobility_functions(modes) + template + Schedule compute_schedule(const Graph& graph) { - } - - inline double round_second_decimal(double x) - { - return std::round(x * 100.0) / 100.0; - } - - void advance(double t_max = 1.0) - { - ScalarType dt_first_mobility = - std::accumulate(this->m_graph.edges().begin(), this->m_graph.edges().end(), - std::numeric_limits::max(), [&](ScalarType current_min, const auto& e) { - auto traveltime_per_region = - round_second_decimal(e.property.travel_time / e.property.path.size()); - if (traveltime_per_region < 0.01) - traveltime_per_region = 0.01; - auto start_mobility = - round_second_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - - this->m_graph.nodes()[e.end_node_idx].property.stay_duration); - if (start_mobility < 0) { - start_mobility = 0.; - } - return std::min(current_min, start_mobility); - }); - - // set population to zero in mobility nodes before starting - for (auto& n : this->m_graph.nodes()) { - n.property.mobility_sim.get_result().get_last_value().setZero(); - } - - auto min_dt = 0.01; - double t_begin = this->m_t - 1.; - - // calc schedule for each edge - precompute_schedule(); - while (this->m_t - epsilon < t_max) { - // auto start = std::chrono::system_clock::now(); - - t_begin += 1; - if (this->m_t + dt_first_mobility > t_max) { - dt_first_mobility = t_max - this->m_t; - for (auto& n : this->m_graph.nodes()) { - // m_node_func(this->m_t, dt_first_mobility, n.property); - n.property.base_sim.advance(this->m_t + dt_first_mobility); - } - break; - } - - for (auto& node : this->m_graph.nodes()) { - node.property.mobility_sim.set_integrator(std::make_shared>()); - } - - size_t indx_schedule = 0; - while (t_begin + 1 > this->m_t + 1e-10) { - advance_edges(indx_schedule); - - // first we integrate the nodes in time. Afterwards the update on the edges is done. - // We start with the edges since the values for t0 are given. - advance_local_nodes(indx_schedule); - advance_mobility_nodes(indx_schedule); - - indx_schedule++; - this->m_t += min_dt; - } - // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate - // the state of the indivuals moving between different counties. - // Therefore there can be differences with the states given by the integrator used for the mobility node. - for (auto& n : this->m_graph.nodes()) { - n.property.mobility_sim.get_result().get_last_value().setZero(); - } - } + Schedule schedule; + calculate_edge_schedule(graph, schedule); + calculate_node_schedule(graph, schedule); + return schedule; } private: - MobilityFunctions m_mobility_functions; - - // describes the schedule for each edge, i.e. which node is visited at which time step - std::vector> schedule_edges; - - // defines if people from a edge are currently in a mobility node. This is necessary to determine the correct - // position in the schedule. Otherwise we could add a specific identifier in schedule_edges. - std::vector> mobility_schedule_edges; - - // describes the syncronization steps which are necessary for the integrator in the edges and nodes. - std::vector> mb_int_schedule; - std::vector> ln_int_schedule; - - // defines the edges on which mobility takes place for each time step - std::vector> edges_mobility; - - // same for the nodes - std::vector> nodes_mobility; - std::vector> nodes_mobility_m; - - // first mobility activites per edge - std::vector first_mobility; - - const double epsilon = 1e-10; - - void precompute_schedule() + /** + * @brief Calculates the edge schedule for a given graph. + * + * This function computes the vectors schedule_edges and mobility_schedule_edges for each edge in the graph. + * The schedule_eges describes for each time step in which node_id the individuals are located. + * The mobility_schedule_edges describes for each time step if the individuals are in a mobility node or not. + * + * @tparam Graph The type of the graph. + * @param graph The graph object containing the nodes and edges. + * @param schedule The Schedule object to store the computed schedules. + */ + template + void calculate_edge_schedule(const Graph& graph, Schedule& schedule) { - const size_t timesteps = 100; - schedule_edges.reserve(this->m_graph.edges().size()); - mobility_schedule_edges.reserve(this->m_graph.edges().size()); + schedule.schedule_edges.reserve(graph.edges().size()); + schedule.mobility_schedule_edges.reserve(graph.edges().size()); // calculate the schedule for each edge - for (auto& e : this->m_graph.edges()) { - // Initialize the schedule and mobility node vectors for the edge - std::vector schedule(timesteps, 0); + for (auto& e : graph.edges()) { + std::vector tmp_schedule(timesteps, 0); std::vector is_mobility_node(timesteps, false); // Calculate travel time per region, ensuring a minimum value of 0.01 @@ -405,17 +327,17 @@ class GraphSimulationExtended : public GraphSimulationBase // Calculate the start time for mobility, ensuring it is not negative const double start_mobility = std::max(0.0, round_second_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - - this->m_graph.nodes()[e.end_node_idx].property.stay_duration)); + graph.nodes()[e.end_node_idx].property.stay_duration)); // Calculate the arrival time at the destination node const double arrive_at = start_mobility + traveltime_per_region * e.property.path.size(); - // Lambda to fill the schedule vector with a specific value over a range + // Lambda to fill the schedule vector with the node index during the trip to the destination. auto fill_schedule = [&](size_t start_idx, size_t end_idx, size_t value) { - std::fill(schedule.begin() + start_idx, schedule.begin() + end_idx, value); + std::fill(tmp_schedule.begin() + start_idx, tmp_schedule.begin() + end_idx, value); }; - // Lambda to fill the mobility node vector with a boolean value over a range + // Lambda to fill the schedule for the mobility models with a bool during the trip to the destination. auto fill_mobility = [&](size_t start_idx, size_t end_idx, bool value) { std::fill(is_mobility_node.begin() + start_idx, is_mobility_node.begin() + end_idx, value); }; @@ -455,25 +377,46 @@ class GraphSimulationExtended : public GraphSimulationBase size_t count_true = std::count(is_mobility_node.begin(), is_mobility_node.end(), true); // Create a reversed path segment for the return trip - std::vector path_reversed(schedule.begin() + first_index, - schedule.begin() + first_index + count_true); + std::vector path_reversed(tmp_schedule.begin() + first_index, + tmp_schedule.begin() + first_index + count_true); std::reverse(path_reversed.begin(), path_reversed.end()); // Copy the reversed path segment to the end of the schedule for the return trip - std::copy(path_reversed.begin(), path_reversed.end(), schedule.end() - count_true); + std::copy(path_reversed.begin(), path_reversed.end(), tmp_schedule.end() - count_true); // Add the schedule and mobility node vectors to their respective containers - schedule_edges.push_back(std::move(schedule)); - mobility_schedule_edges.push_back(std::move(is_mobility_node)); + schedule.schedule_edges.push_back(std::move(tmp_schedule)); + schedule.mobility_schedule_edges.push_back(std::move(is_mobility_node)); } else { log_error("Error in creating schedule."); } } + } + + /** + * @brief Calculates the node schedule for a given graph. + * + * This function computes the vectors local_int_schedule, mobility_int_schedule, edges_mobility, nodes_mobility, + * nodes_mobility_m and first_mobility. + * The local_int_schedule vector describes the time steps where we need to synchronize the integrator in the local models. + * The mobility_int_schedule vector describes the time steps where we need to synchronize the integrator in the mobility models. + * The edges_mobility vector describes all edges where mobility takes place for each time step. + * The nodes_mobility vector describes all local models where mobility takes place for each time step. + * The nodes_mobility_m vector describes all mobility models where mobility takes place at the beginning of each time step. + * The first_mobility vector describes the first time step where mobility takes place for each edge. + * + * @tparam Graph The type of the graph object. + * @param graph The graph object containing the nodes and edges. + * @param schedule The Schedule object to store the computed schedules. + */ + template + void calculate_node_schedule(const Graph& graph, Schedule& schedule) + { // iterate over nodes to create the integration schedule for each node. The integration schedule is necessary // to determine the correct time step for the integrator in the nodes. - for (size_t indx_node = 0; indx_node < this->m_graph.nodes().size(); ++indx_node) { + for (size_t indx_node = 0; indx_node < graph.nodes().size(); ++indx_node) { // Local node initialization with starting at t=0 std::vector order_local_node = {0}; std::vector indx_edges; @@ -481,9 +424,9 @@ class GraphSimulationExtended : public GraphSimulationBase // Find edges starting from the current node and not in mobility at t=0 auto find_edges = [&](size_t t, bool mobility) { std::vector edges; - for (size_t indx_edge = 0; indx_edge < schedule_edges.size(); ++indx_edge) { - if (schedule_edges[indx_edge][t] == indx_node && - mobility_schedule_edges[indx_edge][t] == mobility) { + for (size_t indx_edge = 0; indx_edge < schedule.schedule_edges.size(); ++indx_edge) { + if (schedule.schedule_edges[indx_edge][t] == indx_node && + schedule.mobility_schedule_edges[indx_edge][t] == mobility) { edges.push_back(indx_edge); } } @@ -507,7 +450,7 @@ class GraphSimulationExtended : public GraphSimulationBase if (order_local_node.back() != timesteps - 1) { order_local_node.push_back(timesteps - 1); } - ln_int_schedule.push_back(order_local_node); + schedule.local_int_schedule.push_back(order_local_node); // Mobility node initialization std::vector order_mobility_node; @@ -527,103 +470,222 @@ class GraphSimulationExtended : public GraphSimulationBase } } - mb_int_schedule.push_back(order_mobility_node); + schedule.mobility_int_schedule.push_back(order_mobility_node); } // Reserve space for first_mobility vector and initialize it - first_mobility.reserve(this->m_graph.edges().size()); - for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { - first_mobility[indx_edge] = std::distance( - mobility_schedule_edges[indx_edge].begin(), - std::find(mobility_schedule_edges[indx_edge].begin(), mobility_schedule_edges[indx_edge].end(), true)); + schedule.first_mobility.reserve(graph.edges().size()); + for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { + schedule.first_mobility[indx_edge] = + std::distance(schedule.mobility_schedule_edges[indx_edge].begin(), + std::find(schedule.mobility_schedule_edges[indx_edge].begin(), + schedule.mobility_schedule_edges[indx_edge].end(), true)); } // Reserve space for mobility-related vectors - edges_mobility.reserve(timesteps); - nodes_mobility.reserve(timesteps); - nodes_mobility_m.reserve(timesteps); + schedule.edges_mobility.reserve(timesteps); + schedule.nodes_mobility.reserve(timesteps); + schedule.nodes_mobility_m.reserve(timesteps); // Handle the case where indx_current = 0 separately std::vector temp_edges_mobility; - for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { - if (first_mobility[indx_edge] == 0) { + for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { + if (schedule.first_mobility[indx_edge] == 0) { temp_edges_mobility.push_back(indx_edge); } } - edges_mobility.push_back(std::move(temp_edges_mobility)); + schedule.edges_mobility.push_back(std::move(temp_edges_mobility)); // Initialize nodes_mobility with all node indices for t=0 - std::vector temp_nodes_mobility(this->m_graph.nodes().size()); + std::vector temp_nodes_mobility(graph.nodes().size()); std::iota(temp_nodes_mobility.begin(), temp_nodes_mobility.end(), 0); - nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); + schedule.nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); // Initialize nodes_mobility_m with nodes that have mobility activities at t=0 std::vector temp_nodes_mobility_m; - for (size_t node_indx = 0; node_indx < this->m_graph.nodes().size(); ++node_indx) { - if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), 0)) { + for (size_t node_indx = 0; node_indx < graph.nodes().size(); ++node_indx) { + if (std::binary_search(schedule.mobility_int_schedule[node_indx].begin(), + schedule.mobility_int_schedule[node_indx].end(), 0)) { temp_nodes_mobility_m.push_back(node_indx); } } - nodes_mobility_m.push_back(temp_nodes_mobility_m); + schedule.nodes_mobility_m.push_back(temp_nodes_mobility_m); for (size_t indx_current = 1; indx_current < timesteps; ++indx_current) { std::vector temp_edge_mobility; - for (size_t indx_edge = 0; indx_edge < this->m_graph.edges().size(); ++indx_edge) { - size_t current_node_indx = schedule_edges[indx_edge][indx_current]; - if (indx_current >= first_mobility[indx_edge]) { - if (mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(mb_int_schedule[current_node_indx].begin(), - mb_int_schedule[current_node_indx].end(), indx_current)) { + for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { + size_t current_node_indx = schedule.schedule_edges[indx_edge][indx_current]; + if (indx_current >= schedule.first_mobility[indx_edge]) { + if (schedule.mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(schedule.mobility_int_schedule[current_node_indx].begin(), + schedule.mobility_int_schedule[current_node_indx].end(), indx_current)) { temp_edge_mobility.push_back(indx_edge); } - else if (!mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(ln_int_schedule[current_node_indx].begin(), - ln_int_schedule[current_node_indx].end(), indx_current)) { + else if (!schedule.mobility_schedule_edges[indx_edge][indx_current] && + std::binary_search(schedule.local_int_schedule[current_node_indx].begin(), + schedule.local_int_schedule[current_node_indx].end(), indx_current)) { temp_edge_mobility.push_back(indx_edge); } } } - edges_mobility.push_back(temp_edge_mobility); + schedule.edges_mobility.push_back(temp_edge_mobility); // Clear and fill temp_nodes_mobility and temp_nodes_mobility_m for current timestep temp_nodes_mobility.clear(); temp_nodes_mobility_m.clear(); - for (size_t node_indx = 0; node_indx < this->m_graph.nodes().size(); ++node_indx) { - if (std::binary_search(ln_int_schedule[node_indx].begin(), ln_int_schedule[node_indx].end(), - indx_current)) { + for (size_t node_indx = 0; node_indx < graph.nodes().size(); ++node_indx) { + if (std::binary_search(schedule.local_int_schedule[node_indx].begin(), + schedule.local_int_schedule[node_indx].end(), indx_current)) { temp_nodes_mobility.push_back(node_indx); } - if (std::binary_search(mb_int_schedule[node_indx].begin(), mb_int_schedule[node_indx].end(), - indx_current)) { + if (std::binary_search(schedule.mobility_int_schedule[node_indx].begin(), + schedule.mobility_int_schedule[node_indx].end(), indx_current)) { temp_nodes_mobility_m.push_back(node_indx); } } - nodes_mobility.push_back(temp_nodes_mobility); - nodes_mobility_m.push_back(temp_nodes_mobility_m); + schedule.nodes_mobility.push_back(temp_nodes_mobility); + schedule.nodes_mobility_m.push_back(temp_nodes_mobility_m); } } + /** + * @brief + * + * @param x + * @return double + */ + double round_second_decimal(double x) + { + return std::round(x * 100.0) / 100.0; + } + + size_t timesteps; + double epsilon; +}; + +template +class GraphSimulationExtended : public GraphSimulationBase +{ +public: + using node_function = std::function; + using edge_function = + std::function; + + GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, + MobilityFunctions modes) + : GraphSimulationBase(t0, dt, g, node_func, {}) + , m_mobility_functions(modes) + { + ScheduleManager schedule_manager(100); // Assuming 100 timesteps + schedules = schedule_manager.compute_schedule(this->m_graph); + } + + GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MobilityFunctions modes) + : GraphSimulationBase(t0, dt, std::move(g), node_func, {}) + , m_mobility_functions(modes) + { + ScheduleManager schedule_manager(100); // Assuming 100 timesteps + schedules = schedule_manager.compute_schedule(this->m_graph); + } + + inline double round_second_decimal(double x) + { + return std::round(x * 100.0) / 100.0; + } + + void advance(double t_max = 1.0) + { + ScalarType dt_first_mobility = + std::accumulate(this->m_graph.edges().begin(), this->m_graph.edges().end(), + std::numeric_limits::max(), [&](ScalarType current_min, const auto& e) { + auto traveltime_per_region = + round_second_decimal(e.property.travel_time / e.property.path.size()); + if (traveltime_per_region < 0.01) + traveltime_per_region = 0.01; + auto start_mobility = + round_second_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - + this->m_graph.nodes()[e.end_node_idx].property.stay_duration); + if (start_mobility < 0) { + start_mobility = 0.; + } + return std::min(current_min, start_mobility); + }); + + // set population to zero in mobility nodes before starting + for (auto& n : this->m_graph.nodes()) { + n.property.mobility_sim.get_result().get_last_value().setZero(); + } + + auto min_dt = 0.01; + double t_begin = this->m_t - 1.; + + while (this->m_t - epsilon < t_max) { + // auto start = std::chrono::system_clock::now(); + + t_begin += 1; + if (this->m_t + dt_first_mobility > t_max) { + dt_first_mobility = t_max - this->m_t; + for (auto& n : this->m_graph.nodes()) { + // m_node_func(this->m_t, dt_first_mobility, n.property); + n.property.base_sim.advance(this->m_t + dt_first_mobility); + } + break; + } + + for (auto& node : this->m_graph.nodes()) { + node.property.mobility_sim.set_integrator(std::make_shared>()); + } + + size_t indx_schedule = 0; + while (t_begin + 1 > this->m_t + 1e-10) { + advance_edges(indx_schedule); + + // first we integrate the nodes in time. Afterwards the update on the edges is done. + // We start with the edges since the values for t0 are given. + advance_local_nodes(indx_schedule); + advance_mobility_nodes(indx_schedule); + + indx_schedule++; + this->m_t += min_dt; + } + // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate + // the state of the indivuals moving between different counties. + // Therefore there can be differences with the states given by the integrator used for the mobility node. + for (auto& n : this->m_graph.nodes()) { + n.property.mobility_sim.get_result().get_last_value().setZero(); + } + } + } + +private: + MobilityFunctions m_mobility_functions; + ScheduleManager::Schedule schedules; + const double epsilon = 1e-10; + void advance_edges(size_t indx_schedule) { - for (const auto& edge_indx : edges_mobility[indx_schedule]) { + for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { auto& e = this->m_graph.edges()[edge_indx]; // first mobility activity - if (indx_schedule == first_mobility[edge_indx]) { - auto& node_from = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; - auto& node_to = this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; + if (indx_schedule == schedules.first_mobility[edge_indx]) { + auto& node_from = + this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; + auto& node_to = + this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); m_mobility_functions.init_mobility(this->m_t, 0.0, e.property, node_from, node_to); } // next mobility activity - else if (indx_schedule > first_mobility[edge_indx]) { - auto current_node_indx = schedule_edges[edge_indx][indx_schedule]; - bool in_mobility_node = mobility_schedule_edges[edge_indx][indx_schedule]; + else if (indx_schedule > schedules.first_mobility[edge_indx]) { + auto current_node_indx = schedules.schedule_edges[edge_indx][indx_schedule]; + bool in_mobility_node = schedules.mobility_schedule_edges[edge_indx][indx_schedule]; // determine dt, which is equal to the last integration/syncronization point in the current node - auto integrator_schedule_row = ln_int_schedule[current_node_indx]; + auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; if (in_mobility_node) - integrator_schedule_row = mb_int_schedule[current_node_indx]; + integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; // search the index of indx_schedule in the integrator schedule const size_t indx_current = std::distance( integrator_schedule_row.begin(), @@ -649,20 +711,24 @@ class GraphSimulationExtended : public GraphSimulationBase // We have two cases. Either, we want to send the individuals to the next node, or we just want // to update their state since a syncronization step is necessary in the current node. - if ((schedule_edges[edge_indx][indx_schedule] != schedule_edges[edge_indx][indx_schedule - 1]) || - (mobility_schedule_edges[edge_indx][indx_schedule] != - mobility_schedule_edges[edge_indx][indx_schedule - 1])) { + if ((schedules.schedule_edges[edge_indx][indx_schedule] != + schedules.schedule_edges[edge_indx][indx_schedule - 1]) || + (schedules.mobility_schedule_edges[edge_indx][indx_schedule] != + schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1])) { auto& node_from = - mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.mobility_sim - : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; - - auto& node_to = - mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim - : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.base_sim; - - if (indx_schedule < mobility_schedule_edges[edge_indx].size() - 1) { + schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + .property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + .property.base_sim; + + auto& node_to = schedules.mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.base_sim; + + if (indx_schedule < schedules.mobility_schedule_edges[edge_indx].size() - 1) { // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); m_mobility_functions.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); } @@ -674,14 +740,17 @@ class GraphSimulationExtended : public GraphSimulationBase } else { auto& node_from = - mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.mobility_sim - : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; - - auto& node_to = - mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.mobility_sim - : this->m_graph.nodes()[schedule_edges[edge_indx][indx_schedule]].property.base_sim; + schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + .property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + .property.base_sim; + + auto& node_to = schedules.mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.base_sim; assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); @@ -693,14 +762,16 @@ class GraphSimulationExtended : public GraphSimulationBase void advance_local_nodes(size_t indx_schedule) { - for (const auto& n_indx : nodes_mobility[indx_schedule]) { - auto& n = this->m_graph.nodes()[n_indx]; - const size_t indx_current = std::distance( - ln_int_schedule[n_indx].begin(), - std::lower_bound(ln_int_schedule[n_indx].begin(), ln_int_schedule[n_indx].end(), indx_schedule)); - - const size_t val_next = - (indx_current == ln_int_schedule[n_indx].size() - 1) ? 100 : ln_int_schedule[n_indx][indx_current + 1]; + for (const auto& n_indx : schedules.nodes_mobility[indx_schedule]) { + auto& n = this->m_graph.nodes()[n_indx]; + const size_t indx_current = + std::distance(schedules.local_int_schedule[n_indx].begin(), + std::lower_bound(schedules.local_int_schedule[n_indx].begin(), + schedules.local_int_schedule[n_indx].end(), indx_schedule)); + + const size_t val_next = (indx_current == schedules.local_int_schedule[n_indx].size() - 1) + ? 100 + : schedules.local_int_schedule[n_indx][indx_current + 1]; const ScalarType next_dt = round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); n.property.base_sim.advance(this->m_t + next_dt); @@ -710,13 +781,15 @@ class GraphSimulationExtended : public GraphSimulationBase void advance_mobility_nodes(size_t indx_schedule) { - for (const size_t& n_indx : nodes_mobility_m[indx_schedule]) { - auto& n = this->m_graph.nodes()[n_indx]; - const size_t indx_current = std::distance( - mb_int_schedule[n_indx].begin(), - std::lower_bound(mb_int_schedule[n_indx].begin(), mb_int_schedule[n_indx].end(), indx_schedule)); - const size_t val_next = - (indx_current == mb_int_schedule[n_indx].size() - 1) ? 100 : mb_int_schedule[n_indx][indx_current + 1]; + for (const size_t& n_indx : schedules.nodes_mobility_m[indx_schedule]) { + auto& n = this->m_graph.nodes()[n_indx]; + const size_t indx_current = + std::distance(schedules.mobility_int_schedule[n_indx].begin(), + std::lower_bound(schedules.mobility_int_schedule[n_indx].begin(), + schedules.mobility_int_schedule[n_indx].end(), indx_schedule)); + const size_t val_next = (indx_current == schedules.mobility_int_schedule[n_indx].size() - 1) + ? 100 + : schedules.mobility_int_schedule[n_indx][indx_current + 1]; const ScalarType next_dt = round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); @@ -929,7 +1002,6 @@ void update_status_migrated(Eigen::Ref::Vector> migrated // return success(); // } - // template // class ParameterStudyDetailed : public ParameterStudy // { From 35b6c0149168afcd6bb833c772eeb2550cb05a29 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:39:35 +0200 Subject: [PATCH 05/26] unit tests for schedule, move round function --- cpp/memilio/math/floating_point.h | 15 ++ .../metapopulation_mobility_detailed.h | 60 ++--- cpp/tests/CMakeLists.txt | 1 + cpp/tests/test_math_floating_point.cpp | 31 +++ cpp/tests/test_mobility_detailed.cpp | 236 ++++++++++++++++++ 5 files changed, 308 insertions(+), 35 deletions(-) create mode 100644 cpp/tests/test_mobility_detailed.cpp diff --git a/cpp/memilio/math/floating_point.h b/cpp/memilio/math/floating_point.h index e4bb47351b..f825a33d52 100644 --- a/cpp/memilio/math/floating_point.h +++ b/cpp/memilio/math/floating_point.h @@ -136,6 +136,21 @@ bool floating_point_greater_equal(T v1, T v2, T abs_tol = 0, T rel_tol = std::nu return !floating_point_less(v1, v2, abs_tol, rel_tol); } +/** + * @brief Rounds a value to the nearest nth decimal place. + * + * @param x The double value to be rounded. + * @param n The number of decimal places we want to round to. + * @return The rounded double value to n decimal digits. + */ +template +T round_nth_decimal(T x, size_t n) +{ + using std::round; + T factor = std::pow(10.0, n); + return round(x * factor) / factor; +} + } // namespace mio #endif // MIO_MATH_FLOATING_POINT_H diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index d6c278382b..c6762ec985 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -36,6 +36,7 @@ #include "memilio/utils/date.h" #include "memilio/mobility/graph.h" #include "memilio/io/mobility_io.h" +#include "memilio/math/floating_point.h" #include "boost/filesystem.hpp" @@ -297,7 +298,6 @@ class ScheduleManager return schedule; } -private: /** * @brief Calculates the edge schedule for a given graph. * @@ -322,12 +322,13 @@ class ScheduleManager // Calculate travel time per region, ensuring a minimum value of 0.01 const double traveltime_per_region = - std::max(0.01, round_second_decimal(e.property.travel_time / e.property.path.size())); + std::max(0.01, round_nth_decimal(e.property.travel_time / e.property.path.size(), 2)); // Calculate the start time for mobility, ensuring it is not negative const double start_mobility = - std::max(0.0, round_second_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - - graph.nodes()[e.end_node_idx].property.stay_duration)); + std::max(0.0, round_nth_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - + graph.nodes()[e.end_node_idx].property.stay_duration, + 2)); // Calculate the arrival time at the destination node const double arrive_at = start_mobility + traveltime_per_region * e.property.path.size(); @@ -476,10 +477,14 @@ class ScheduleManager // Reserve space for first_mobility vector and initialize it schedule.first_mobility.reserve(graph.edges().size()); for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { - schedule.first_mobility[indx_edge] = - std::distance(schedule.mobility_schedule_edges[indx_edge].begin(), - std::find(schedule.mobility_schedule_edges[indx_edge].begin(), - schedule.mobility_schedule_edges[indx_edge].end(), true)); + // find the first time step where mobility takes place. If there is no mobility, it is set the size of the schedule. + size_t index_time = 0; + for (; index_time < schedule.mobility_schedule_edges[indx_edge].size(); ++index_time) { + if (schedule.mobility_schedule_edges[indx_edge][index_time]) { + break; + } + } + schedule.first_mobility.push_back(index_time); } // Reserve space for mobility-related vectors @@ -549,17 +554,6 @@ class ScheduleManager } } - /** - * @brief - * - * @param x - * @return double - */ - double round_second_decimal(double x) - { - return std::round(x * 100.0) / 100.0; - } - size_t timesteps; double epsilon; }; @@ -590,23 +584,19 @@ class GraphSimulationExtended : public GraphSimulationBase schedules = schedule_manager.compute_schedule(this->m_graph); } - inline double round_second_decimal(double x) - { - return std::round(x * 100.0) / 100.0; - } - void advance(double t_max = 1.0) { ScalarType dt_first_mobility = std::accumulate(this->m_graph.edges().begin(), this->m_graph.edges().end(), std::numeric_limits::max(), [&](ScalarType current_min, const auto& e) { auto traveltime_per_region = - round_second_decimal(e.property.travel_time / e.property.path.size()); + round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); if (traveltime_per_region < 0.01) traveltime_per_region = 0.01; auto start_mobility = - round_second_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - - this->m_graph.nodes()[e.end_node_idx].property.stay_duration); + round_nth_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - + this->m_graph.nodes()[e.end_node_idx].property.stay_duration, + 2); if (start_mobility < 0) { start_mobility = 0.; } @@ -697,16 +687,16 @@ class GraphSimulationExtended : public GraphSimulationBase ScalarType dt_mobility; if (indx_current == 0) { - dt_mobility = round_second_decimal(e.property.travel_time / e.property.path.size()); + dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); if (dt_mobility < 0.01) dt_mobility = 0.01; } else { - dt_mobility = - round_second_decimal((static_cast(integrator_schedule_row[indx_current]) - - static_cast(integrator_schedule_row[indx_current - 1])) / - 100 + - epsilon); + dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current]) - + static_cast(integrator_schedule_row[indx_current - 1])) / + 100 + + epsilon, + 2); } // We have two cases. Either, we want to send the individuals to the next node, or we just want @@ -773,7 +763,7 @@ class GraphSimulationExtended : public GraphSimulationBase ? 100 : schedules.local_int_schedule[n_indx][indx_current + 1]; const ScalarType next_dt = - round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + round_nth_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon, 2); n.property.base_sim.advance(this->m_t + next_dt); // m_node_func(this->m_t, next_dt, n.property.base_sim); } @@ -791,7 +781,7 @@ class GraphSimulationExtended : public GraphSimulationBase ? 100 : schedules.mobility_int_schedule[n_indx][indx_current + 1]; const ScalarType next_dt = - round_second_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon); + round_nth_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon, 2); // get all time points from the last integration step auto& last_time_point = n.property.mobility_sim.get_result().get_time( diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 5f05c28d12..754c350a14 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -17,6 +17,7 @@ set(TESTSOURCES test_sde_sir.cpp test_sde_sirs.cpp test_mobility.cpp + test_mobility_detailed.cpp test_date.cpp test_eigen_util.cpp test_odesecir_ageres.cpp diff --git a/cpp/tests/test_math_floating_point.cpp b/cpp/tests/test_math_floating_point.cpp index dbadfbca54..127ff50bd1 100644 --- a/cpp/tests/test_math_floating_point.cpp +++ b/cpp/tests/test_math_floating_point.cpp @@ -169,3 +169,34 @@ TYPED_TEST(TestMathFloatingPoint, floating_point_greater_equal) }}; test_fp_compare(this->a, this->b, this->params, &mio::floating_point_greater_equal, truth_table); } + +TYPED_TEST(TestMathFloatingPoint, round_nth_decimal) +{ + using FP = double; + FP value = static_cast(1.234567); + EXPECT_EQ(mio::round_nth_decimal(value, 0), static_cast(1)); + EXPECT_EQ(mio::round_nth_decimal(value, 1), static_cast(1.2)); + EXPECT_EQ(mio::round_nth_decimal(value, 2), static_cast(1.23)); + EXPECT_EQ(mio::round_nth_decimal(value, 3), static_cast(1.235)); + EXPECT_EQ(mio::round_nth_decimal(value, 4), static_cast(1.2346)); + EXPECT_EQ(mio::round_nth_decimal(value, 5), static_cast(1.23457)); + EXPECT_EQ(mio::round_nth_decimal(value, 6), static_cast(1.234567)); + + value = static_cast(-1.234567); + EXPECT_EQ(mio::round_nth_decimal(value, 0), static_cast(-1)); + EXPECT_EQ(mio::round_nth_decimal(value, 1), static_cast(-1.2)); + EXPECT_EQ(mio::round_nth_decimal(value, 2), static_cast(-1.23)); + EXPECT_EQ(mio::round_nth_decimal(value, 3), static_cast(-1.235)); + EXPECT_EQ(mio::round_nth_decimal(value, 4), static_cast(-1.2346)); + EXPECT_EQ(mio::round_nth_decimal(value, 5), static_cast(-1.23457)); + EXPECT_EQ(mio::round_nth_decimal(value, 6), static_cast(-1.234567)); + + value = static_cast(0.999999); + EXPECT_EQ(mio::round_nth_decimal(value, 0), static_cast(1)); + EXPECT_EQ(mio::round_nth_decimal(value, 1), static_cast(1.0)); + EXPECT_EQ(mio::round_nth_decimal(value, 2), static_cast(1.0)); + EXPECT_EQ(mio::round_nth_decimal(value, 3), static_cast(1.0)); + EXPECT_EQ(mio::round_nth_decimal(value, 4), static_cast(1.0)); + EXPECT_EQ(mio::round_nth_decimal(value, 5), static_cast(1.0)); + EXPECT_EQ(mio::round_nth_decimal(value, 6), static_cast(0.999999)); +} diff --git a/cpp/tests/test_mobility_detailed.cpp b/cpp/tests/test_mobility_detailed.cpp new file mode 100644 index 0000000000..05d6693311 --- /dev/null +++ b/cpp/tests/test_mobility_detailed.cpp @@ -0,0 +1,236 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "memilio/mobility/metapopulation_mobility_detailed.h" +#include "matchers.h" + +#include + +// Mock classes for Graph, Node, and Edge +struct NodeProperty { + double stay_duration; +}; + +struct EdgeProperty { + double travel_time; + std::vector path; +}; + +struct Node { + NodeProperty property; +}; + +struct Edge { + size_t start_node_idx; + size_t end_node_idx; + EdgeProperty property; +}; + +class MockGraph +{ +public: + const std::vector& nodes() const + { + return nodes_; + } + + const std::vector& edges() const + { + return edges_; + } + + void add_node(const Node& node) + { + nodes_.push_back(node); + } + + void add_edge(const Edge& edge) + { + edges_.push_back(edge); + } + +private: + std::vector nodes_; + std::vector edges_; +}; + +class ScheduleManagerTest : public ::testing::Test +{ +protected: + ScheduleManagerTest() + : manager(100) + { + } + + mio::ScheduleManager manager; +}; + +// Test case for computing the schedule with a simple graph +TEST_F(ScheduleManagerTest, ComputeSimpleSchedule) +{ + MockGraph graph; + + Node node1 = {NodeProperty{0.4}}; + Node node2 = {NodeProperty{0.4}}; + graph.add_node(node1); + graph.add_node(node2); + + Edge edge1 = {0, 1, EdgeProperty{0.1, {0, 1}}}; + Edge edge2 = {1, 0, EdgeProperty{0.1, {1, 0}}}; + graph.add_edge(edge1); + graph.add_edge(edge2); + + auto schedule = manager.compute_schedule(graph); + + // Check that the schedule edges and mobility schedule edges are correctly filled + EXPECT_EQ(schedule.schedule_edges.size(), 2); + EXPECT_EQ(schedule.mobility_schedule_edges.size(), 2); + + // Check the contents of the schedule + for (const auto& edge_schedule : schedule.schedule_edges) { + EXPECT_EQ(edge_schedule.size(), 100); // timesteps + } + + for (const auto& mobility_schedule : schedule.mobility_schedule_edges) { + EXPECT_EQ(mobility_schedule.size(), 100); // timesteps + } +} + +// Test case for computing the schedule with no edges +TEST_F(ScheduleManagerTest, ComputeEmptySchedule) +{ + MockGraph graph; + + Node node1 = {NodeProperty{1.0}}; + Node node2 = {NodeProperty{1.0}}; + graph.add_node(node1); + graph.add_node(node2); + + auto schedule = manager.compute_schedule(graph); + + // Check that the schedule edges and mobility schedule edges are empty + EXPECT_TRUE(schedule.schedule_edges.empty()); + EXPECT_TRUE(schedule.mobility_schedule_edges.empty()); +} + +// Test case for verifying first_mobility vector +TEST_F(ScheduleManagerTest, VerifyFirstMobility) +{ + MockGraph graph; + + Node node1 = {NodeProperty{0.5}}; + Node node2 = {NodeProperty{0.5}}; + graph.add_node(node1); + graph.add_node(node2); + + Edge edge1 = {0, 1, EdgeProperty{0.2, {0, 1}}}; + graph.add_edge(edge1); + + auto schedule = manager.compute_schedule(graph); + + // First mobility should be at timestep 10 + EXPECT_EQ(schedule.first_mobility.size(), 1); + EXPECT_EQ(schedule.first_mobility[0], 10); +} + +// Test case for verifying local and mobility integration schedules +TEST_F(ScheduleManagerTest, VerifyIntegrationSchedules) +{ + MockGraph graph; + + Node node1 = {NodeProperty{0.4}}; + Node node2 = {NodeProperty{0.4}}; + graph.add_node(node1); + graph.add_node(node2); + + Edge edge1 = {0, 1, EdgeProperty{0.1, {0, 1}}}; + graph.add_edge(edge1); + + auto schedule = manager.compute_schedule(graph); + + // Check that local and mobility integration schedules are filled + EXPECT_EQ(schedule.local_int_schedule.size(), 2); + EXPECT_EQ(schedule.mobility_int_schedule.size(), 2); + + // the local_int_schedule for the first node should contain only the start, end time and the start time of the mobility. + EXPECT_EQ(schedule.local_int_schedule[0].size(), 3); + EXPECT_EQ(schedule.local_int_schedule[0][0], 0); + EXPECT_EQ(schedule.local_int_schedule[0][1], 40); + EXPECT_EQ(schedule.local_int_schedule[0][2], 99); + + // the second node should have the start and end time and additionally the arrival and departure time of the + // individual from the first node. + EXPECT_EQ(schedule.local_int_schedule[1].size(), 4); + EXPECT_EQ(schedule.local_int_schedule[1][0], 0); + EXPECT_EQ(schedule.local_int_schedule[1][1], 50); + EXPECT_EQ(schedule.local_int_schedule[1][2], 90); + EXPECT_EQ(schedule.local_int_schedule[1][3], 99); + + // since the travel time is 0.1, the mobility_int_schedule for the first node should contain the start time of the mobility, + // the time where the individual arrives at the second mobility model and the time where the individual arrives again before getting back. + // the last time step isnt considered since we do this by default at the end of the day. + EXPECT_EQ(schedule.mobility_int_schedule[0].size(), 3); + EXPECT_EQ(schedule.mobility_int_schedule[0][0], 40); + EXPECT_EQ(schedule.mobility_int_schedule[0][1], 45); + EXPECT_EQ(schedule.mobility_int_schedule[0][2], 95); + + // for the second node, all times should be shifted by 5 and add the time point where the individual start their home trip + EXPECT_EQ(schedule.mobility_int_schedule[1].size(), 4); + EXPECT_EQ(schedule.mobility_int_schedule[1][0], 45); + EXPECT_EQ(schedule.mobility_int_schedule[1][1], 50); + EXPECT_EQ(schedule.mobility_int_schedule[1][2], 90); + EXPECT_EQ(schedule.mobility_int_schedule[1][3], 95); +} + +// Test case where travel time per region lower than allowed minimum of 0.01 +TEST_F(ScheduleManagerTest, MinimalTravelTimePerRegion) +{ + MockGraph graph; + + Node node1 = {NodeProperty{0.4}}; + Node node2 = {NodeProperty{0.4}}; + graph.add_node(node1); + graph.add_node(node2); + + Edge edge1 = {0, 1, EdgeProperty{0.001, {0, 1}}}; // Minimal travel time per region + graph.add_edge(edge1); + + auto schedule = manager.compute_schedule(graph); + + // Check that the schedule edges and mobility schedule edges are correctly filled + EXPECT_EQ(schedule.schedule_edges.size(), 1); + EXPECT_EQ(schedule.mobility_schedule_edges.size(), 1); + + // Check that the travel time was correctly set to the minimum value + EXPECT_EQ(schedule.mobility_schedule_edges[0][56], true); + EXPECT_EQ(schedule.mobility_schedule_edges[0][57], true); + EXPECT_EQ(schedule.mobility_schedule_edges[0][98], true); + EXPECT_EQ(schedule.mobility_schedule_edges[0][99], true); + + // check that the number of true values is 4 + size_t count = 0; + for (const auto& mobility_schedule : schedule.mobility_schedule_edges) { + for (const auto& mobility : mobility_schedule) { + if (mobility) { + count++; + } + } + } + EXPECT_EQ(count, 4); +} \ No newline at end of file From 7389a1c59037041e37059e06b9308d1cb2d0f3ec Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:08:36 +0200 Subject: [PATCH 06/26] remove duplicate --- .../metapopulation_mobility_detailed.h | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index c6762ec985..7c97b9db8f 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -98,11 +98,6 @@ class ExtendedMigrationEdge : public MigrationEdge template using ExtendedGraph = Graph, ExtendedMigrationEdge>; -// Detect if get_migration_factors is defined for Sim -template -using get_migration_factors_expr_t = decltype(get_migration_factors( - std::declval(), std::declval(), std::declval&>())); - // Default implementation when get_migration_factors is not defined for Sim template ::value, void*> = nullptr> auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref& y) @@ -736,13 +731,14 @@ class GraphSimulationExtended : public GraphSimulationBase : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] .property.base_sim; - auto& node_to = schedules.mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] - .property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] - .property.base_sim; - - assert(node_from.get_result().get_last_value() == node_to.get_result().get_last_value()); + assert(node_from.get_result().get_last_value() == + (schedules.mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + .property.base_sim) + .get_result() + .get_last_value()); // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); m_mobility_functions.update_only(this->m_t, dt_mobility, e.property, node_from); } From c0f7ed88460a505f2258e15aaeb4ea6d4096113b Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:54:33 +0200 Subject: [PATCH 07/26] reset end of the day, prints in example --- cpp/examples/ode_seir_detailed_mobility.cpp | 15 +++- .../metapopulation_mobility_detailed.h | 74 +++++++++++++------ 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index 6daa8ec8d1..8c9732d157 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -77,12 +77,23 @@ int main() // results node 1 std::cout << "Results node 1" << std::endl; auto interpolated_sim1 = - mio::interpolate_simulation_result(sim.get_graph().nodes()[1].property.base_sim.get_result()); + mio::interpolate_simulation_result(sim.get_graph().nodes()[0].property.base_sim.get_result()); interpolated_sim1.print_table({"S", "E", "I", "R"}); // results node 1 mobility_sim std::cout << "Mobility results node 1" << std::endl; - auto interpolated_sim1_mobility = sim.get_graph().nodes()[1].property.mobility_sim.get_result(); + auto interpolated_sim1_mobility = sim.get_graph().nodes()[0].property.mobility_sim.get_result(); interpolated_sim1_mobility.print_table({"S", "E", "I", "R"}); + + // results node 2 + std::cout << "Results node 2" << std::endl; + auto interpolated_sim2 = sim.get_graph().nodes()[1].property.base_sim.get_result(); + interpolated_sim2.print_table({"S", "E", "I", "R"}); + + // results node 2 mobility_sim + std::cout << "Mobility results node 2" << std::endl; + auto interpolated_sim2_mobility = sim.get_graph().nodes()[1].property.mobility_sim.get_result(); + interpolated_sim2_mobility.print_table({"S", "E", "I", "R"}); + return 0; } diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 7c97b9db8f..47cd72bc41 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -116,6 +116,7 @@ template void move_migrated(Eigen::Ref> migrated, Eigen::Ref> results_from, Eigen::Ref> results_to) { + // before moving the commuters, we need to look for negative values in migrated and correct them. const auto group = 6; const auto num_comparts = results_to.size() / group; @@ -245,21 +246,14 @@ class MobilityFunctions from_sim.get_result().get_last_value()); } - void move_and_delete(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void move_and_delete(ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) { - Eigen::Index idx = edge.get_return_times().get_num_time_points() - 1; - auto& integrator_node = from_sim.get_integrator(); - update_status_migrated(edge.get_migrated()[idx], from_sim, integrator_node, - from_sim.get_result().get_last_value(), t, dt); - move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), to_sim.get_result().get_last_value()); for (Eigen::Index i = edge.get_return_times().get_num_time_points() - 1; i >= 0; --i) { - if (edge.get_return_times().get_time(i) <= t) { - edge.get_migrated().remove_time_point(i); - edge.get_return_times().remove_time_point(i); - } + edge.get_migrated().remove_time_point(i); + edge.get_return_times().remove_time_point(i); } } }; @@ -606,14 +600,11 @@ class GraphSimulationExtended : public GraphSimulationBase auto min_dt = 0.01; double t_begin = this->m_t - 1.; - while (this->m_t - epsilon < t_max) { - // auto start = std::chrono::system_clock::now(); - + while (this->m_t < t_max - epsilon) { t_begin += 1; if (this->m_t + dt_first_mobility > t_max) { dt_first_mobility = t_max - this->m_t; for (auto& n : this->m_graph.nodes()) { - // m_node_func(this->m_t, dt_first_mobility, n.property); n.property.base_sim.advance(this->m_t + dt_first_mobility); } break; @@ -717,11 +708,6 @@ class GraphSimulationExtended : public GraphSimulationBase // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); m_mobility_functions.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); } - else { - // the last time step is handled differently since we have to close the timeseries - // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 2); - m_mobility_functions.move_and_delete(this->m_t, dt_mobility, e.property, node_from, node_to); - } } else { auto& node_from = @@ -744,6 +730,46 @@ class GraphSimulationExtended : public GraphSimulationBase } } } + // in the last time step we have to move the individuals back to their local model + if (indx_schedule == 99) { + auto edge_index = 0; + for (auto& e : this->m_graph.edges()) { + auto current_node_indx = schedules.schedule_edges[edge_index][indx_schedule]; + bool in_mobility_node = schedules.mobility_schedule_edges[edge_index][indx_schedule]; + + // determine dt, which is equal to the last integration/syncronization point in the current node + auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; + if (in_mobility_node) + integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; + // search the index of indx_schedule in the integrator schedule + const size_t indx_current = std::distance( + integrator_schedule_row.begin(), + std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); + + ScalarType dt_mobility; + if (indx_current == 0) { + dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); + if (dt_mobility < 0.01) + dt_mobility = 0.01; + } + else { + dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current]) - + static_cast(integrator_schedule_row[indx_current - 1])) / + 100 + + epsilon, + 2); + } + + auto& node_from = + this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.mobility_sim; + + auto& node_to = + this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.base_sim; + + m_mobility_functions.move_and_delete(e.property, node_from, node_to); + edge_index++; + } + } } void advance_local_nodes(size_t indx_schedule) @@ -769,10 +795,12 @@ class GraphSimulationExtended : public GraphSimulationBase { for (const size_t& n_indx : schedules.nodes_mobility_m[indx_schedule]) { auto& n = this->m_graph.nodes()[n_indx]; + // determine in which index of mobility_int_schedule we are const size_t indx_current = std::distance(schedules.mobility_int_schedule[n_indx].begin(), std::lower_bound(schedules.mobility_int_schedule[n_indx].begin(), schedules.mobility_int_schedule[n_indx].end(), indx_schedule)); + // determine the next time step const size_t val_next = (indx_current == schedules.mobility_int_schedule[n_indx].size() - 1) ? 100 : schedules.mobility_int_schedule[n_indx][indx_current + 1]; @@ -782,12 +810,14 @@ class GraphSimulationExtended : public GraphSimulationBase // get all time points from the last integration step auto& last_time_point = n.property.mobility_sim.get_result().get_time( n.property.mobility_sim.get_result().get_num_time_points() - 1); - // wenn last_time_point nicht innerhalb eines intervalls von 1-e10 von t liegt, dann setzte den letzten Zeitpunkt auf m_t + // if the last time point is not within an interval of 1-e10 from t, then set the last time point to m_t if (std::fabs(last_time_point - this->m_t) > 1e-10) { n.property.mobility_sim.get_result().get_last_time() = this->m_t; } - // m_node_func(this->m_t, next_dt, n.property.mobility_sim); - n.property.mobility_sim.advance(this->m_t + next_dt); + // only advance in time if there are individuals in the mobility model + if (n.property.mobility_sim.get_result().get_last_value().sum() > 1e-8) { + n.property.mobility_sim.advance(this->m_t + next_dt); + } Eigen::Index indx_min; while (n.property.mobility_sim.get_result().get_last_value().minCoeff(&indx_min) < -1e-7) { Eigen::Index indx_max; From 3ee3efb9de8b97b04aa82e19a65ac10b89046659 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:53:55 +0200 Subject: [PATCH 08/26] set nodes and set edges, new simulation --- .../metapopulation_mobility_detailed.h | 282 ++++---- .../2022_omicron_late_phase_mobility.cpp | 680 ++++++++++++++++++ cpp/simulations/CMakeLists.txt | 4 + 3 files changed, 826 insertions(+), 140 deletions(-) create mode 100644 cpp/simulations/2022_omicron_late_phase_mobility.cpp diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 47cd72bc41..3866e001af 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -869,154 +869,156 @@ void update_status_migrated(Eigen::Ref::Vector> migrated sim.get_model().set_flow_values(flows_model); } -// /** -// * @brief Sets the graph nodes for counties or districts. -// * Reads the node ids which could refer to districts or counties and the epidemiological -// * data from json files and creates one node for each id. Every node contains a model. -// * @param[in] params Model Parameters that are used for every node. -// * @param[in] start_date Start date for which the data should be read. -// * @param[in] end_data End date for which the data should be read. -// * @param[in] data_dir Directory that contains the data files. -// * @param[in] population_data_path Path to json file containing the population data. -// * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. -// * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). -// * @param[in, out] params_graph Graph whose nodes are set by the function. -// * @param[in] read_func Function that reads input data for german counties and sets Model compartments. -// * @param[in] node_func Function that returns the county ids. -// * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. -// * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. -// * @param[in] tnt_capacity_factor Factor for test and trace capacity. -// * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. -// * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. -// */ -// template -// IOResult set_nodes_detailed(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, -// const std::string& population_data_path, const std::string& stay_times_data_path, -// bool is_node_for_county, GraphDetailed& params_graph, -// ReadFunction&& read_func, NodeIdFunction&& node_func, -// const std::vector& scaling_factor_inf, double scaling_factor_icu, -// double tnt_capacity_factor, int num_days = 0, bool export_time_series = false) -// { +/** + * @brief Sets the graph nodes for counties or districts. + * Reads the node ids which could refer to districts or counties and the epidemiological + * data from json files and creates one node for each id. Every node contains a model. + * @param[in] params Model Parameters that are used for every node. + * @param[in] start_date Start date for which the data should be read. + * @param[in] end_data End date for which the data should be read. + * @param[in] data_dir Directory that contains the data files. + * @param[in] population_data_path Path to json file containing the population data. + * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. + * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] read_func Function that reads input data for german counties and sets Model compartments. + * @param[in] node_func Function that returns the county ids. + * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. + * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. + * @param[in] tnt_capacity_factor Factor for test and trace capacity. + * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. + * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. + */ +template +IOResult set_nodes(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, + const std::string& population_data_path, const std::string& stay_times_data_path, + bool is_node_for_county, mio::ExtendedGraph& params_graph, ReadFunction&& read_func, + NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, + double scaling_factor_icu, double tnt_capacity_factor, int num_days = 0, + bool export_time_series = false, bool rki_age_groups = true) +{ -// BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); -// BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county)); + BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); -// std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); + BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county, rki_age_groups)); + std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); + for (auto& node : nodes) { + node.parameters = params; + } -// for (auto& node : nodes) { -// node.parameters = params; -// } -// BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), -// num_days, export_time_series)); - -// for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { - -// auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; - -// //local parameters -// auto& tnt_value = nodes[node_idx].parameters.template get(); -// tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); -// tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); - -// //holiday periods -// auto id = int(mio::regions::CountyId(node_ids[node_idx])); -// auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); -// auto& contacts = nodes[node_idx].parameters.template get(); -// contacts.get_school_holidays() = -// std::vector>(holiday_periods.size()); -// std::transform( -// holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { -// return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), -// mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); -// }); - -// //uncertainty in populations -// for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { -// for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { -// auto& compartment_value = nodes[node_idx].populations[{i, j}]; -// compartment_value = -// mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); -// compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), -// 1.1 * double(compartment_value))); -// } -// } + BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), + num_days, export_time_series)); + + for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { + + auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; + + //local parameters + auto& tnt_value = nodes[node_idx].parameters.template get(); + tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); + tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); + + //holiday periods + auto id = int(mio::regions::CountyId(node_ids[node_idx])); + auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); + auto& contacts = nodes[node_idx].parameters.template get(); + contacts.get_school_holidays() = + std::vector>(holiday_periods.size()); + std::transform( + holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { + return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), + mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); + }); + + //uncertainty in populations + for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { + for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { + auto& compartment_value = nodes[node_idx].populations[{i, j}]; + compartment_value = + mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); + compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), + 1.1 * double(compartment_value))); + } + } -// // Add mobility node -// auto mobility = nodes[node_idx]; -// mobility.populations.set_total(0); + // Add mobility node + auto mobility_model = nodes[node_idx]; + mobility_model.populations.set_total(0); -// params_graph.add_node(node_ids[node_idx], duration_stay((Eigen::Index)node_idx), nodes[node_idx], mobility); -// } -// return success(); -// } - -// /** -// * @brief Sets the graph edges. -// * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. -// * @param[in] travel_times_path Path to txt file containing the travel times between counties. -// * @param[in] mobility_data_path Path to txt file containing the commuting matrices. -// * @param[in] travelpath_path Path to txt file containing the paths between counties. -// * @param[in, out] params_graph Graph whose nodes are set by the function. -// * @param[in] migrating_compartments Compartments that commute. -// * @param[in] contact_locations_size Number of contact locations. -// * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. -// */ -// template -// IOResult -// set_edges_detailed(const std::string& travel_times_path, const std::string mobility_data_path, -// const std::string& travelpath_path, GraphDetailed& params_graph, -// std::initializer_list& migrating_compartments, size_t contact_locations_size, -// std::vector commuting_weights = std::vector{}, -// ScalarType theshold_edges = 4e-5) -// { -// BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_path)); -// BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_path)); -// BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travelpath_path)); - -// for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { -// for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { -// auto& populations = params_graph.nodes()[county_idx_i].property.populations; - -// // mobility coefficients have the same number of components as the contact matrices. -// // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) -// auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); -// auto num_age_groups = (size_t)params_graph.nodes()[county_idx_i].property.parameters.get_num_groups(); -// commuting_weights = -// (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); - -// //commuters -// auto working_population = 0.0; -// auto min_commuter_age = mio::AgeGroup(2); -// auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes -// for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { -// working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; -// } -// auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; -// for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { -// for (auto compartment : migrating_compartments) { -// auto coeff_index = populations.get_flat_index({age, compartment}); -// mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = -// commuter_coeff_ij * commuting_weights[size_t(age)]; -// } -// } + params_graph.add_node(1, nodes[node_idx], mobility_model, duration_stay((Eigen::Index)node_idx)); + } + return success(); +} -// auto path = path_mobility[county_idx_i][county_idx_j]; -// if (static_cast(path[0]) != county_idx_i || -// static_cast(path[path.size() - 1]) != county_idx_j) -// std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; +/** + * @brief Sets the graph edges. + * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. + * @param[in] travel_times_path Path to txt file containing the travel times between counties. + * @param[in] mobility_data_path Path to txt file containing the commuting matrices. + * @param[in] travelpath_path Path to txt file containing the paths between counties. + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] migrating_compartments Compartments that commute. + * @param[in] contact_locations_size Number of contact locations. + * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. + */ +template +IOResult set_edges(const std::string& travel_times_dir, const std::string mobility_data_dir, + const std::string& travel_path_dir, mio::ExtendedGraph& params_graph, + std::initializer_list& migrating_compartments, size_t contact_locations_size, + std::vector commuting_weights = std::vector{}, + ScalarType theshold_edges = 4e-5) +{ + BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); + BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_dir)); + BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travel_path_dir)); + + for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { + for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { + auto& populations = params_graph.nodes()[county_idx_i].property.base_sim.populations; + + // mobility coefficients have the same number of components as the contact matrices. + // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) + auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); + auto num_age_groups = + (size_t)params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups(); + commuting_weights = + (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); + + //commuters + auto working_population = 0.0; + auto min_commuter_age = mio::AgeGroup(2); + auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; + } + auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + for (auto compartment : migrating_compartments) { + auto coeff_index = populations.get_flat_index({age, compartment}); + mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = + commuter_coeff_ij * commuting_weights[size_t(age)]; + } + } -// //only add edges with mobility above thresholds for performance -// if (commuter_coeff_ij > theshold_edges) { -// params_graph.add_edge(county_idx_i, county_idx_j, travel_times(county_idx_i, county_idx_j), -// path_mobility[county_idx_i][county_idx_j], std::move(mobility_coeffs)); -// } -// } -// } + auto path = path_mobility[county_idx_i][county_idx_j]; + if (static_cast(path[0]) != county_idx_i || + static_cast(path[path.size() - 1]) != county_idx_j) + std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; + + //only add edges with mobility above thresholds for performance + if (commuter_coeff_ij > theshold_edges) { + params_graph.add_edge(county_idx_i, county_idx_j, std::move(mobility_coeffs), + travel_times(county_idx_i, county_idx_j), + path_mobility[county_idx_i][county_idx_j]); + // g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); + } + } + } -// return success(); -// } + return success(); +} // template // class ParameterStudyDetailed : public ParameterStudy diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp new file mode 100644 index 0000000000..a7a36e7434 --- /dev/null +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -0,0 +1,680 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "memilio/compartments/parameter_studies.h" +#include "memilio/geography/regions.h" +#include "memilio/io/epi_data.h" +#include "memilio/io/result_io.h" +#include "memilio/io/mobility_io.h" +#include "memilio/utils/date.h" +#include "memilio/utils/miompi.h" +#include "memilio/utils/random_number_generator.h" +#include "ode_secirvvs/parameters_io.h" +#include "ode_secirvvs/parameter_space.h" +#include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/mobility/metapopulation_mobility_detailed.h" +#include "memilio/utils/stl_util.h" +#include "boost/filesystem.hpp" +#include +#include + +namespace fs = boost::filesystem; + +/** + * Set a value and distribution of an UncertainValue. + * Assigns average of min and max as a value and UNIFORM(min, max) as a distribution. + * @param p uncertain value to set. + * @param min minimum of distribution. + * @param max minimum of distribution. + */ +void assign_uniform_distribution(mio::UncertainValue& p, double min, double max) +{ + p = mio::UncertainValue(0.5 * (max + min)); + p.set_distribution(mio::ParameterDistributionUniform(min, max)); +} + +/** + * Set a value and distribution of an array of UncertainValues. + * Assigns average of min[i] and max[i] as a value and UNIFORM(min[i], max[i]) as a distribution for + * each element i of the array. + * @param array array of UncertainValues to set. + * @param min minimum of distribution for each element of array. + * @param max minimum of distribution for each element of array. + */ +template +void array_assign_uniform_distribution(mio::CustomIndexArray, mio::AgeGroup>& array, + const double (&min)[N], const double (&max)[N]) +{ + assert(N == array.numel()); + for (auto i = mio::AgeGroup(0); i < mio::AgeGroup(N); ++i) { + assign_uniform_distribution(array[i], min[size_t(i)], max[size_t(i)]); + } +} + +/** + * Set a value and distribution of an array of UncertainValues. + * Assigns average of min and max as a value and UNIFORM(min, max) as a distribution to every element of the array. + * @param array array of UncertainValues to set. + * @param min minimum of distribution. + * @param max minimum of distribution. + */ +void array_assign_uniform_distribution(mio::CustomIndexArray, mio::AgeGroup>& array, + double min, double max) +{ + for (auto i = mio::AgeGroup(0); i < array.size(); ++i) { + assign_uniform_distribution(array[i], min, max); + } +} + +/** + * Set epidemiological parameters of Covid19. + * @param params Object that the parameters will be added to. + * @returns Currently generates no errors. + */ +mio::IOResult set_covid_parameters(mio::osecirvvs::Parameters& params) +{ + //times + // doi.org/10.1016/j.lanepe.2022.100446 , doi.org/10.3201/eid2806.220158 + const double timeExposedMin = 1.66; + const double timeExposedMax = 1.66; + const double timeInfectedNoSymptomsMin = 1.44; + const double timeInfectedNoSymptomsMax = 1.44; + + const double timeInfectedSymptomsMin = 6.58; //https://doi.org/10.1016/S0140-6736(22)00327-0 + const double timeInfectedSymptomsMax = 7.16; //https://doi.org/10.1016/S0140-6736(22)00327-0 + const double timeInfectedSevereMin[] = {1.8, 1.8, 1.8, 2.5, 3.5, 4.91}; // doi.org/10.1186/s12879-022-07971-6 + const double timeInfectedSevereMax[] = {2.3, 2.3, 2.3, 3.67, 5, 7.01}; // doi.org/10.1186/s12879-022-07971-6 + + const double timeInfectedCriticalMin[] = {9.29, 9.29, 9.29, + 10.842, 11.15, 11.07}; // https://doi.org/10.1186/s12879-022-07971-6 + const double timeInfectedCriticalMax[] = {10.57, 10.57, 10.57, + 12.86, 13.23, 13.25}; // https://doi.org/10.1186/s12879-022-07971-6 + + array_assign_uniform_distribution(params.get>(), timeExposedMin, + timeExposedMax); + array_assign_uniform_distribution(params.get>(), + timeInfectedNoSymptomsMin, timeInfectedNoSymptomsMax); + array_assign_uniform_distribution(params.get>(), + timeInfectedSymptomsMin, timeInfectedSymptomsMax); + array_assign_uniform_distribution(params.get>(), timeInfectedSevereMin, + timeInfectedSevereMax); + array_assign_uniform_distribution(params.get>(), + timeInfectedCriticalMin, timeInfectedCriticalMax); + + //probabilities + double fac_variant = 1.5; //https://doi.org/10.7554/eLife.78933 + const double transmissionProbabilityOnContactMin[] = {0.02 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, + 0.05 * fac_variant, 0.08 * fac_variant, 0.1 * fac_variant}; + + const double transmissionProbabilityOnContactMax[] = {0.04 * fac_variant, 0.07 * fac_variant, 0.07 * fac_variant, + 0.07 * fac_variant, 0.10 * fac_variant, 0.15 * fac_variant}; + + const double relativeTransmissionNoSymptomsMin = 0.5; + + //{0.6, 0.55, 0.65,0.7, 0.75, 0.85}; // DOI: 10.1097/INF.0000000000003791 + const double relativeTransmissionNoSymptomsMax = 0.5; + // {0.8, 0.75, 0.8,0.8, 0.825, 0.9}; // DOI: 10.1097/INF.0000000000003791 + const double riskOfInfectionFromSymptomaticMin = 0.0; // beta (depends on incidence and test and trace capacity) + const double riskOfInfectionFromSymptomaticMax = 0.2; + const double maxRiskOfInfectionFromSymptomaticMin = 0.4; + const double maxRiskOfInfectionFromSymptomaticMax = 0.5; + + // DOI: 10.1097/INF.0000000000003791 geht hier auch. aber ähnliche werte + const double recoveredPerInfectedNoSymptomsMin[] = {0.2, 0.25, 0.2, + 0.2, 0.175, 0.1}; // doi.org/10.1101/2022.05.05.22274697 + const double recoveredPerInfectedNoSymptomsMax[] = {0.4, 0.45, 0.35, + 0.3, 0.25, 0.15}; // doi.org/10.1101/2022.05.05.22274697 + + // 56% weniger risiko ins krankenhaus doi:10.1136/bmjgh-2023-0123 + // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10347449/pdf/bmjgh-2023-012328.pdf + // alternativ: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9321237/pdf/vaccines-10-01049.pdf + + // Faktoren aus https://www.thelancet.com/journals/lancet/article/PIIS0140-6736(22)00462-7/fulltext + const double severePerInfectedSymptomsMin[] = {1 * 0.006, 0.8 * 0.006, 0.4 * 0.015, + 0.3 * 0.049, 0.25 * 0.15, 0.35 * 0.2}; // 2021 paper + const double severePerInfectedSymptomsMax[] = {1 * 0.009, 0.8 * 0.009, 0.4 * 0.023, 0.3 * 0.074, + 0.25 * 0.18, 0.35 * 0.25}; // quelle 2021 paper + factors + + // const double criticalPerSevereMin[] = { + // 0.0511, 0.0686, 0.0491, 0.114, + // 0.1495, 0.0674}; // www.sozialministerium.at/dam/jcr:f472e977-e1bf-415f-95e1-35a1b53e608d/Factsheet_Coronavirus_Hospitalisierungen.pdf + // const double criticalPerSevereMax[] = { + // 0.0511, 0.0686, 0.0491, 0.114, + // 0.1495, 0.0674}; // www.sozialministerium.at/dam/jcr:f472e977-e1bf-415f-95e1-35a1b53e608d/Factsheet_Coronavirus_Hospitalisierungen.pdf + + // delta paper + // risk of icu admission https://doi.org/10.1177/14034948221108548 + const double fac_icu = 0.52; + const double criticalPerSevereMin[] = {0.05 * fac_icu, 0.05 * fac_icu, 0.05 * fac_icu, + 0.10 * fac_icu, 0.25 * fac_icu, 0.35 * fac_icu}; + const double criticalPerSevereMax[] = {0.10 * fac_icu, 0.10 * fac_icu, 0.10 * fac_icu, + 0.20 * fac_icu, 0.35 * fac_icu, 0.45 * fac_icu}; + + // 61% weniger risiko zu sterben doi:10.1136/bmjgh-2023-0123 + // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10347449/pdf/bmjgh-2023-012328.pdf + const double fac_dead = 0.39; + const double deathsPerCriticalMin[] = {fac_dead * 0.00, fac_dead * 0.00, fac_dead * 0.10, + fac_dead * 0.10, fac_dead * 0.30, fac_dead * 0.5}; // 2021 paper + const double deathsPerCriticalMax[] = {fac_dead * 0.10, fac_dead * 0.10, fac_dead * 0.18, + fac_dead * 0.18, fac_dead * 0.50, fac_dead * 0.7}; + + // alternative https://doi.org/10.1136/bmj-2022-070695 + // const double fac_dead_u59 = 0.14; + // const double fac_dead_p59 = 0.44; + // const double deathsPerCriticalMin[] = {fac_dead_u59 * 0.00, fac_dead_u59 * 0.00, fac_dead_u59 * 0.10, + // fac_dead_u59 * 0.10, fac_dead_p59 * 0.30, fac_dead_p59 * 0.5}; // 2021 paper + // const double deathsPerCriticalMax[] = {fac_dead_u59 * 0.10, fac_dead_u59 * 0.10, fac_dead_u59 * 0.18, + // fac_dead_u59 * 0.18, fac_dead_p59 * 0.50, fac_dead_p59 * 0.7}; + + const double reducExposedPartialImmunityMin = 1.0; //0.569; // doi.org/10.1136/bmj-2022-071502 + const double reducExposedPartialImmunityMax = 1.0; // 0.637; // doi.org/10.1136/bmj-2022-071502 + // const double reducExposedImprovedImmunityMin = 0.36; // https://doi.org/10.1038/s41591-023-02219-5 + // const double reducExposedImprovedImmunityMax = 0.66; // https://doi.org/10.1038/s41591-023-02219-5 + + const double reducExposedImprovedImmunityMin = 1.0; + //0.34 * reducExposedPartialImmunityMin; // https://jamanetwork.com/journals/jama/fullarticle/2788487 0.19346 + const double reducExposedImprovedImmunityMax = 1.0; + //0.34 * reducExposedPartialImmunityMax; // https://jamanetwork.com/journals/jama/fullarticle/2788487 0.21658 + + const double reducInfectedSymptomsPartialImmunityMin = 0.746; // doi.org/10.1056/NEJMoa2119451 + const double reducInfectedSymptomsPartialImmunityMax = 0.961; // doi.org/10.1056/NEJMoa2119451 + const double reducInfectedSymptomsImprovedImmunityMin = 0.295; // doi.org/10.1056/NEJMoa2119451 + const double reducInfectedSymptomsImprovedImmunityMax = 0.344; // doi.org/10.1056/NEJMoa2119451 + + const double reducInfectedSevereCriticalDeadPartialImmunityMin = + 0.52; // www.assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1050721/Vaccine-surveillance-report-week-4.pdf + const double reducInfectedSevereCriticalDeadPartialImmunityMax = + 0.82; // www.assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1050721/Vaccine-surveillance-report-week-4.pdf + const double reducInfectedSevereCriticalDeadImprovedImmunityMin = 0.1; // doi.org/10.1136/bmj-2022-071502 + const double reducInfectedSevereCriticalDeadImprovedImmunityMax = 0.19; // doi.org/10.1136/bmj-2022-071502 + const double reducTimeInfectedMild = 0.5; // doi.org/10.1101/2021.09.24.21263978 + + array_assign_uniform_distribution(params.get>(), + transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); + array_assign_uniform_distribution(params.get>(), + relativeTransmissionNoSymptomsMin, relativeTransmissionNoSymptomsMax); + array_assign_uniform_distribution(params.get>(), + riskOfInfectionFromSymptomaticMin, riskOfInfectionFromSymptomaticMax); + array_assign_uniform_distribution(params.get>(), + maxRiskOfInfectionFromSymptomaticMin, maxRiskOfInfectionFromSymptomaticMax); + array_assign_uniform_distribution(params.get>(), + recoveredPerInfectedNoSymptomsMin, recoveredPerInfectedNoSymptomsMax); + array_assign_uniform_distribution(params.get>(), + severePerInfectedSymptomsMin, severePerInfectedSymptomsMax); + array_assign_uniform_distribution(params.get>(), criticalPerSevereMin, + criticalPerSevereMax); + array_assign_uniform_distribution(params.get>(), deathsPerCriticalMin, + deathsPerCriticalMax); + + array_assign_uniform_distribution(params.get>(), + reducExposedPartialImmunityMin, reducExposedPartialImmunityMax); + array_assign_uniform_distribution(params.get>(), + reducExposedImprovedImmunityMin, reducExposedImprovedImmunityMax); + array_assign_uniform_distribution(params.get>(), + reducInfectedSymptomsPartialImmunityMin, reducInfectedSymptomsPartialImmunityMax); + array_assign_uniform_distribution(params.get>(), + reducInfectedSymptomsImprovedImmunityMin, + reducInfectedSymptomsImprovedImmunityMax); + array_assign_uniform_distribution( + params.get>(), + reducInfectedSevereCriticalDeadPartialImmunityMin, reducInfectedSevereCriticalDeadPartialImmunityMax); + array_assign_uniform_distribution( + params.get>(), + reducInfectedSevereCriticalDeadImprovedImmunityMin, reducInfectedSevereCriticalDeadImprovedImmunityMax); + array_assign_uniform_distribution(params.get>(), + reducTimeInfectedMild, reducTimeInfectedMild); + + //sasonality + const double seasonality_min = 0.1; + const double seasonality_max = 0.3; + + assign_uniform_distribution(params.get>(), seasonality_min, seasonality_max); + + // Delta specific parameter + params.get() = mio::get_day_in_year(mio::Date(2021, 6, 6)); + + return mio::success(); +} + +enum class ContactLocation +{ + Home = 0, + School, + Work, + Other, + Count, +}; + +/** + * different types of NPI, used as DampingType. + */ +enum class Intervention +{ + Home, + SchoolClosure, + HomeOffice, + GatheringBanFacilitiesClosure, + PhysicalDistanceAndMasks, + SeniorAwareness, +}; + +/** + * different level of NPI, used as DampingLevel. + */ +enum class InterventionLevel +{ + Main, + PhysicalDistanceAndMasks, + SeniorAwareness, + Holidays, +}; + +static const std::map contact_locations = {{ContactLocation::Home, "home"}, + {ContactLocation::School, "school_pf_eig"}, + {ContactLocation::Work, "work"}, + {ContactLocation::Other, "other"}}; + +/** + * Set contact matrices. + * Reads contact matrices from files in the data directory. + * @param data_dir data directory. + * @param params Object that the contact matrices will be added to. + * @returns any io errors that happen during reading of the files. + */ +mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirvvs::Parameters& params, + ScalarType scale_contacts = 1.0, ScalarType share_staying = 1.0) +{ + auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); + auto contact_matrix_transport = contact_transport_status.value(); + auto contact_matrices = mio::ContactMatrixGroup(contact_locations.size(), size_t(params.get_num_groups())); + for (auto&& contact_location : contact_locations) { + BOOST_OUTCOME_TRY(auto&& baseline, + mio::read_mobility_plain( + (data_dir / "contacts" / ("baseline_" + contact_location.second + ".txt")).string())); + + if (contact_location.second == "other") { + contact_matrices[size_t(contact_location.first)].get_baseline() = + (1 - share_staying) * abs(baseline - contact_matrix_transport) / scale_contacts + + share_staying * abs(baseline - contact_matrix_transport); + contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + } + else { + contact_matrices[size_t(contact_location.first)].get_baseline() = + (1 - share_staying) * baseline / scale_contacts + share_staying * baseline; + contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + } + } + params.get>() = mio::UncertainContactMatrix(contact_matrices); + + return mio::success(); +} + +mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirvvs::Parameters& params, + ScalarType scale_contacts = 1.0) +{ + auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); + auto contact_matrix_transport = contact_transport_status.value(); + auto contact_matrices = mio::ContactMatrixGroup(1, size_t(params.get_num_groups())); + // ScalarType const polymod_share_contacts_transport = 1 / 0.2770885028949545; + + contact_matrices[0].get_baseline() = contact_matrix_transport / scale_contacts; + contact_matrices[0].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + + params.get>() = mio::UncertainContactMatrix(contact_matrices); + + return mio::success(); +} + +// reset population in graph +void init_pop_cologne_szenario(mio::Graph, mio::MigrationParameters>& graph, + const int id_cologne) +{ + std::vector> immunity = {{0.04, 0.61, 0.35}, {0.04, 0.61, 0.35}, {0.075, 0.62, 0.305}, + {0.08, 0.62, 0.3}, {0.035, 0.58, 0.385}, {0.01, 0.41, 0.58}}; + + for (auto& node : graph.nodes()) { + for (auto age = mio::AgeGroup(0); age < mio::AgeGroup(6); age++) { + auto pop_age = 0.0; + for (auto inf_state = mio::Index(0); + inf_state < mio::osecirvvs::InfectionState::Count; ++inf_state) { + pop_age += node.property.populations[{age, inf_state}]; + node.property.populations[{age, inf_state}] = 0.0; + } + size_t immunity_index = static_cast(age); + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] = + pop_age * immunity[immunity_index][0]; + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] = + pop_age * immunity[immunity_index][1]; + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] = + pop_age * immunity[immunity_index][2]; + } + if (node.id == id_cologne) { + // infect p% of population + ScalarType p = 0.05; + for (auto age = mio::AgeGroup(0); age < graph.nodes()[0].property.parameters.get_num_groups(); age++) { + node.property.populations[{mio::AgeGroup(age), mio::osecirvvs::InfectionState::InfectedSymptomsNaive}] = + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] * p; + node.property.populations[{age, mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity}] = + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] * p; + node.property.populations[{mio::AgeGroup(age), + mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}] = + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] * p; + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] *= (1 - p); + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] *= (1 - p); + node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] *= + (1 - p); + } + } + } +} + +/** + * Create the input graph for the parameter study. + * Reads files from the data directory. + * @param start_date start date of the simulation. + * @param end_date end date of the simulation. + * @param data_dir data directory. + * @returns created graph or any io errors that happen during reading of the files. + */ +mio::IOResult>> +get_graph(const int num_days, const std::string& data_dir, bool masks, bool ffp2, bool szenario_cologne, bool edges) +{ + mio::unused(num_days, masks, ffp2, szenario_cologne, edges); + std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); + std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); + auto start_date = mio::Date(2022, 8, 1); + auto end_date = mio::Date(2022, 11, 1); + + // global parameters + const int num_age_groups = 6; + mio::osecirvvs::Parameters params(num_age_groups); + params.get() = mio::get_day_in_year(start_date); + auto params_status = set_covid_parameters(params); + auto contacts_status = set_contact_matrices(data_dir, params); + params.get() = mio::get_day_in_year(start_date); + + // create graph + mio::ExtendedGraph> params_graph; + + // set nodes + auto scaling_factor_infected = std::vector(size_t(params.get_num_groups()), 1.0); + auto scaling_factor_icu = 1.0; + auto tnt_capacity_factor = 1.43 / 100000.; + const auto& read_function_nodes = mio::osecirvvs::read_input_data_county>; + const auto& node_id_function = mio::get_node_ids; + + auto set_nodes_status = mio::set_nodes, + mio::osecirvvs::ContactPatterns, mio::osecirvvs::Model, + mio::MigrationParameters, mio::osecirvvs::Parameters>( + params, start_date, end_date, data_dir, + mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"), durations_dir, true, + params_graph, read_function_nodes, node_id_function, scaling_factor_infected, scaling_factor_icu, + tnt_capacity_factor, num_days, false); + + if (!set_nodes_status) { + return set_nodes_status.error(); + } + + // set edges + auto migrating_compartments = {mio::osecirvvs::InfectionState::SusceptibleNaive, + mio::osecirvvs::InfectionState::ExposedNaive, + mio::osecirvvs::InfectionState::InfectedNoSymptomsNaive, + mio::osecirvvs::InfectionState::InfectedSymptomsNaive, + mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity, + mio::osecirvvs::InfectionState::SusceptiblePartialImmunity, + mio::osecirvvs::InfectionState::ExposedPartialImmunity, + mio::osecirvvs::InfectionState::InfectedNoSymptomsPartialImmunity, + mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity, + mio::osecirvvs::InfectionState::ExposedImprovedImmunity, + mio::osecirvvs::InfectionState::InfectedNoSymptomsImprovedImmunity, + mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}; + auto set_edges_status = + mio::set_edges, mio::MigrationParameters, + mio::MigrationCoefficientGroup, mio::osecirvvs::InfectionState>( + travel_times_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), + mio::path_join(data_dir, "mobility", "wegketten_ohne_komma.txt"), params_graph, migrating_compartments, + contact_locations.size(), std::vector{0., 0., 1.0, 1.0, 0.33, 0., 0.}); + + if (!set_edges_status) { + return set_edges_status.error(); + } + + return params_graph; +} +mio::IOResult run(const std::string data_dir) +{ + // mio::set_log_level(mio::LogLevel::critical); + const auto num_days = 90; + // const int num_runs = 12; + const bool masks = true; + const bool ffp2 = true; + const bool edges = true; + + // wenn masks false und ffp2 true, dann error ausgeben + if (!masks && ffp2) { + mio::log_error("ffp2 only possible with masks"); + } + + bool szenario_cologne = false; + + // auto params_graph = get_graph(num_days, masks); + BOOST_OUTCOME_TRY(auto&& created, get_graph(num_days, data_dir, masks, ffp2, szenario_cologne, edges)); + auto params_graph = created; + + // std::string res_dir = "/localdata1/code/memilio/results_paper/t90_f145_mask_" + std::to_string(masks); + + // res_dir += std::string(ffp2 ? "_ffp2" : "") + std::string(szenario_cologne ? "_cologne" : "") + + // std::string(!edges ? "_no_edges" : ""); + + // if (mio::mpi::is_root()) + // std::cout << "res_dir = " << res_dir << "\n"; + + // // check if boths dir exist, otherwise create them + // if (!fs::exists(res_dir)) { + // fs::create_directory(res_dir); + // } + // auto write_graph_status = mio::write_graph(params_graph, "/localdata1/code/memilio/save_graph"); + + // std::vector county_ids(params_graph.nodes().size()); + // std::transform(params_graph.nodes().begin(), params_graph.nodes().end(), county_ids.begin(), [](auto& n) { + // return n.id; + // }); + + // // parameter study + // auto parameter_study = mio::ParameterStudy>>( + // params_graph, 0.0, num_days, 0.01, num_runs); + // if (mio::mpi::is_root()) { + // printf("Seeds: "); + // for (auto s : parameter_study.get_rng().get_seeds()) { + // printf("%u, ", s); + // } + // printf("\n"); + // } + // auto save_single_run_result = mio::IOResult(mio::success()); + // auto ensemble = parameter_study.run( + // [&](auto&& graph) { + // return draw_sample(graph, false); + // }, + // [&](auto results_graph, auto&& run_idx) { + // auto interpolated_result = mio::interpolate_simulation_result(results_graph); + + // auto params = std::vector(); + // params.reserve(results_graph.nodes().size()); + // std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), + // [](auto&& node) { + // return node.property.get_simulation().get_model(); + // }); + + // auto tp_mob_node = results_graph.nodes()[0].node_pt.get_simulation().get_flows().get_num_time_points(); + + // // transmission mobility models + // const std::string results_path_mb = res_dir + "/flows_mb"; + // if (!fs::exists(results_path_mb)) { + // fs::create_directory(results_path_mb); + // } + // const std::string results_filename = + // results_path_mb + "/transmission_mobility_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile(results_filename); + + // outfile << "Node_from Node_to Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.edges().size(); ++i) { + // auto& edge = results_graph.edges()[i]; + // outfile << edge.start_node_idx << " " << edge.end_node_idx << " "; + // const auto size_vec = edge.infecions_mobility.size(); + // for (size_t j = 0; j < size_vec; ++j) { + // outfile << edge.infecions_mobility[j] << " "; + // } + // outfile << "\n"; + // } + // outfile.close(); + + // // transmission commuter in local models + // const std::string results_filename_local_transmission = + // results_path_mb + "/transmission_local_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile_transmission_local(results_filename_local_transmission); + + // outfile_transmission_local << "Node_from Node_to Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.edges().size(); ++i) { + // auto& edge = results_graph.edges()[i]; + // outfile_transmission_local << edge.start_node_idx << " " << edge.end_node_idx << " "; + // const auto size_vec = edge.infecions_local_model.size(); + // for (size_t j = 0; j < size_vec; ++j) { + // outfile_transmission_local << edge.infecions_local_model[j] << " "; + // } + // outfile_transmission_local << "\n"; + // } + // outfile_transmission_local.close(); + + // // symptomatic infections commuter in mobility models + // const std::string results_filename_mobility_symp = + // results_path_mb + "/symptomps_mobility_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile_symp_mobility(results_filename_mobility_symp); + + // outfile_symp_mobility << "Node_from Node_to Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.edges().size(); ++i) { + // auto& edge = results_graph.edges()[i]; + // outfile_symp_mobility << edge.start_node_idx << " " << edge.end_node_idx << " "; + // const auto size_vec = edge.infecions_symptomatic_mobility.size(); + // for (size_t j = 0; j < size_vec; ++j) { + // outfile_symp_mobility << edge.infecions_symptomatic_mobility[j] << " "; + // } + // outfile_symp_mobility << "\n"; + // } + // outfile_symp_mobility.close(); + + // // symptomatic infections commuter in local models + // const std::string results_filename_local_symp = + // results_path_mb + "/symptomps_local_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile_symp_local(results_filename_local_symp); + + // outfile_symp_local << "Node_from Node_to Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.edges().size(); ++i) { + // auto& edge = results_graph.edges()[i]; + // outfile_symp_local << edge.start_node_idx << " " << edge.end_node_idx << " "; + // const auto size_vec = edge.infecions_symptomatic_local_model.size(); + // for (size_t j = 0; j < size_vec; ++j) { + // outfile_symp_local << edge.infecions_symptomatic_local_model[j] << " "; + // } + // outfile_symp_local << "\n"; + // } + // outfile_symp_local.close(); + + // // symptomatic infections total in local models + // const std::string results_filename_total_symp = + // results_path_mb + "/total_symptomps_local_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile_symp_local_total(results_filename_total_symp); + // std::vector indx_symp = {20, 36, 56, 73, 89, 109, 126, 142, 162, + // 179, 195, 215, 232, 248, 268, 285, 301}; + // outfile_symp_local_total << "Node Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.nodes().size(); ++i) { + // auto& node = results_graph.nodes()[i]; + // auto flows = mio::interpolate_simulation_result(node.property.get_simulation().get_flows()); + // outfile_symp_local_total << i << " "; + // for (auto idx = 0; idx < flows.get_num_time_points(); ++idx) { + // auto flows_step = flows.get_value(idx); + // auto sum_infections = + // std::accumulate(indx_symp.begin(), indx_symp.end(), 0.0, [&flows_step](double sum, int i) { + // return sum + flows_step(i); + // }); + // outfile_symp_local_total << sum_infections << " "; + // } + // outfile_symp_local_total << "\n"; + // } + // outfile_symp_local_total.close(); + + // // transmission infections total in local models + // const std::string results_filename_total_transmission = + // results_path_mb + "/total_transmission_local_run_" + std::to_string(run_idx) + ".txt"; + // std::ofstream outfile_tra_local_total(results_filename_total_transmission); + // std::vector indx_infections = {0, 33, 17, 53, 86, 70, 106, 139, 123, + // 159, 192, 176, 212, 245, 229, 265, 298, 282}; + // outfile_tra_local_total << "Node Infections_mobility_node \n"; + // for (size_t i = 0; i < results_graph.nodes().size(); ++i) { + // auto& node = results_graph.nodes()[i]; + // auto flows = mio::interpolate_simulation_result(node.property.get_simulation().get_flows()); + // outfile_tra_local_total << i << " "; + // for (auto idx = 0; idx < flows.get_num_time_points(); ++idx) { + // auto flows_step = flows.get_value(idx); + // auto sum_infections = std::accumulate(indx_infections.begin(), indx_infections.end(), 0.0, + // [&flows_step](double sum, int i) { + // return sum + flows_step(i); + // }); + // outfile_tra_local_total << sum_infections << " "; + // } + // outfile_tra_local_total << "\n"; + // } + // outfile_tra_local_total.close(); + + // std::cout << "Run " << run_idx << " complete." << std::endl; + + // return std::make_pair(interpolated_result, params); + // }); + + // if (ensemble.size() > 0) { + // auto ensemble_results = std::vector>>{}; + // ensemble_results.reserve(ensemble.size()); + // auto ensemble_params = std::vector>>{}; + // ensemble_params.reserve(ensemble.size()); + // for (auto&& run : ensemble) { + // ensemble_results.emplace_back(std::move(run.first)); + // ensemble_params.emplace_back(std::move(run.second)); + // } + // BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); + // } + + return mio::success(); +} + +int main() +{ + mio::set_log_level(mio::LogLevel::warn); + mio::mpi::init(); + + const std::string data_dir = "/localdata1/code/memilio/data"; + + auto result = run(data_dir); + if (!result) { + printf("%s\n", result.error().formatted_message().c_str()); + mio::mpi::finalize(); + return -1; + } + mio::mpi::finalize(); + return 0; +} \ No newline at end of file diff --git a/cpp/simulations/CMakeLists.txt b/cpp/simulations/CMakeLists.txt index a784b83ba5..9573a4710b 100644 --- a/cpp/simulations/CMakeLists.txt +++ b/cpp/simulations/CMakeLists.txt @@ -7,6 +7,10 @@ if(MEMILIO_HAS_JSONCPP AND MEMILIO_HAS_HDF5) target_link_libraries(2021_vaccination_delta PRIVATE memilio ode_secirvvs Boost::filesystem ${HDF5_C_LIBRARIES}) target_compile_options(2021_vaccination_delta PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + add_executable(2022_omicron_late_phase_mobility 2022_omicron_late_phase_mobility.cpp) + target_link_libraries(2022_omicron_late_phase_mobility PRIVATE memilio ode_secirvvs Boost::filesystem ${HDF5_C_LIBRARIES}) + target_compile_options(2022_omicron_late_phase_mobility PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + add_executable(abm_simulation abm.cpp) target_link_libraries(abm_simulation PRIVATE memilio abm Boost::filesystem ${HDF5_C_LIBRARIES}) target_compile_options(abm_simulation PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) From 9b548b7c91a724ad25deafc0752eef7fa13e7851 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:28:28 +0200 Subject: [PATCH 09/26] example mobility, param study with new graph, sampling mobility params --- cpp/examples/ode_seir_detailed_mobility.cpp | 2 +- cpp/memilio/compartments/parameter_studies.h | 83 +++- .../metapopulation_mobility_detailed.h | 327 +----------- cpp/models/ode_secirvvs/parameter_space.h | 79 +++ .../2022_omicron_late_phase_mobility.cpp | 467 ++++++++++-------- 5 files changed, 399 insertions(+), 559 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index 8c9732d157..3dcbbf9a35 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -71,7 +71,7 @@ int main() g.add_edge(0, 1, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path1); g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); - auto sim = mio::make_extended_migration_sim(t0, dt, std::move(g)); + auto sim = mio::make_migration_sim(t0, dt, std::move(g)); sim.advance(tmax); // results node 1 diff --git a/cpp/memilio/compartments/parameter_studies.h b/cpp/memilio/compartments/parameter_studies.h index e364657bc8..09cb895fd7 100644 --- a/cpp/memilio/compartments/parameter_studies.h +++ b/cpp/memilio/compartments/parameter_studies.h @@ -35,6 +35,35 @@ #include #include +#include + +// Check if a type has a member called 'stay_duration' +template +struct has_stay_duration : std::false_type { +}; + +template +struct has_stay_duration().stay_duration)>> : std::true_type { +}; + +// Check if a type has a member called 'travel_time' +template +struct has_travel_time : std::false_type { +}; + +template +struct has_travel_time().travel_time)>> : std::true_type { +}; + +// Check if a type has a member called 'path' +template +struct has_path : std::false_type { +}; + +template +struct has_path().path)>> : std::true_type { +}; + namespace mio { @@ -42,25 +71,15 @@ namespace mio * Class that performs multiple simulation runs with randomly sampled parameters. * Can simulate migration graphs with one simulation in each node or single simulations. * @tparam S type of simulation that runs in one node of the graph. + * @tparam ParametersGraph stores the parameters of the simulation. This is the input of ParameterStudies. + * @tparam SimulationGraph stores simulations and their results of each run. This is the output of ParameterStudies for each run. */ -template +template >, + class SimulationGraph = Graph, MigrationEdge>> class ParameterStudy { public: - /** - * The type of simulation of a single node of the graph. - */ using Simulation = S; - /** - * The Graph type that stores the parametes of the simulation. - * This is the input of ParameterStudies. - */ - using ParametersGraph = mio::Graph>; - /** - * The Graph type that stores simulations and their results of each run. - * This is the output of ParameterStudies for each run. - */ - using SimulationGraph = mio::Graph, mio::MigrationEdge>; /** * create study for graph of compartment models. @@ -336,23 +355,51 @@ class ParameterStudy } private: - //sample parameters and create simulation template - mio::GraphSimulation create_sampled_simulation(SampleGraphFunction sample_graph) + auto create_sampled_simulation(SampleGraphFunction sample_graph) { SimulationGraph sim_graph; auto sampled_graph = sample_graph(m_graph); for (auto&& node : sampled_graph.nodes()) { - sim_graph.add_node(node.id, node.property, m_t0, m_dt_integration); + using PropertyType = typename std::decay::type; + add_node_with_properties(sim_graph, node, + std::integral_constant::value>{}); } for (auto&& edge : sampled_graph.edges()) { - sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property); + using PropertyType = typename std::decay::type; + add_edge_with_properties(sim_graph, edge, std::integral_constant < bool, + has_travel_time::value&& has_path::value > {}); } return make_migration_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); } + template + void add_node_with_properties(GraphType& graph, const NodeType& node, std::false_type) + { + graph.add_node(node.id, node.property, m_t0, m_dt_integration); + } + + template + void add_node_with_properties(GraphType& graph, const NodeType& node, std::true_type) + { + graph.add_node(node.id, node.property.base_sim, node.property.mobility_sim, node.property.stay_duration); + } + + template + void add_edge_with_properties(GraphType& graph, const EdgeType& edge, std::false_type) + { + graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property); + } + + template + void add_edge_with_properties(GraphType& graph, const EdgeType& edge, std::true_type) + { + graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property.get_parameters(), + edge.property.travel_time, edge.property.path); + } + std::vector distribute_runs(size_t num_runs, int num_procs) { //evenly distribute runs diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 3866e001af..49a31a1b19 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -105,13 +105,6 @@ auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref::value, void*> = nullptr> -auto get_migration_factors(const Sim& sim, double t, const Eigen::Ref& y) -{ - return get_migration_factors(sim, t, y); -} - template void move_migrated(Eigen::Ref> migrated, Eigen::Ref> results_from, Eigen::Ref> results_to) @@ -832,7 +825,7 @@ class GraphSimulationExtended : public GraphSimulationBase template GraphSimulationExtended, ExtendedMigrationEdge>, MobilityFunctions> -make_extended_migration_sim(FP t0, FP dt, Graph, ExtendedMigrationEdge>&& graph) +make_migration_sim(FP t0, FP dt, Graph, ExtendedMigrationEdge>&& graph) { auto migration_modes = MobilityFunctions(); return GraphSimulationExtended, ExtendedMigrationEdge>, @@ -868,324 +861,6 @@ void update_status_migrated(Eigen::Ref::Vector> migrated flows_model *= dt; sim.get_model().set_flow_values(flows_model); } - -/** - * @brief Sets the graph nodes for counties or districts. - * Reads the node ids which could refer to districts or counties and the epidemiological - * data from json files and creates one node for each id. Every node contains a model. - * @param[in] params Model Parameters that are used for every node. - * @param[in] start_date Start date for which the data should be read. - * @param[in] end_data End date for which the data should be read. - * @param[in] data_dir Directory that contains the data files. - * @param[in] population_data_path Path to json file containing the population data. - * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. - * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] read_func Function that reads input data for german counties and sets Model compartments. - * @param[in] node_func Function that returns the county ids. - * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. - * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. - * @param[in] tnt_capacity_factor Factor for test and trace capacity. - * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. - * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. - */ -template -IOResult set_nodes(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, - const std::string& population_data_path, const std::string& stay_times_data_path, - bool is_node_for_county, mio::ExtendedGraph& params_graph, ReadFunction&& read_func, - NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, - double scaling_factor_icu, double tnt_capacity_factor, int num_days = 0, - bool export_time_series = false, bool rki_age_groups = true) -{ - - BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); - - BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county, rki_age_groups)); - std::vector nodes(node_ids.size(), Model(int(size_t(params.get_num_groups())))); - for (auto& node : nodes) { - node.parameters = params; - } - - BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), - num_days, export_time_series)); - - for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { - - auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; - - //local parameters - auto& tnt_value = nodes[node_idx].parameters.template get(); - tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); - tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); - - //holiday periods - auto id = int(mio::regions::CountyId(node_ids[node_idx])); - auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); - auto& contacts = nodes[node_idx].parameters.template get(); - contacts.get_school_holidays() = - std::vector>(holiday_periods.size()); - std::transform( - holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { - return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), - mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); - }); - - //uncertainty in populations - for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { - for (auto j = mio::Index(0); j < Model::Compartments::Count; ++j) { - auto& compartment_value = nodes[node_idx].populations[{i, j}]; - compartment_value = - mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); - compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), - 1.1 * double(compartment_value))); - } - } - - // Add mobility node - auto mobility_model = nodes[node_idx]; - mobility_model.populations.set_total(0); - - params_graph.add_node(1, nodes[node_idx], mobility_model, duration_stay((Eigen::Index)node_idx)); - } - return success(); -} - -/** - * @brief Sets the graph edges. - * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. - * @param[in] travel_times_path Path to txt file containing the travel times between counties. - * @param[in] mobility_data_path Path to txt file containing the commuting matrices. - * @param[in] travelpath_path Path to txt file containing the paths between counties. - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] migrating_compartments Compartments that commute. - * @param[in] contact_locations_size Number of contact locations. - * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. - */ -template -IOResult set_edges(const std::string& travel_times_dir, const std::string mobility_data_dir, - const std::string& travel_path_dir, mio::ExtendedGraph& params_graph, - std::initializer_list& migrating_compartments, size_t contact_locations_size, - std::vector commuting_weights = std::vector{}, - ScalarType theshold_edges = 4e-5) -{ - BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); - BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_dir)); - BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travel_path_dir)); - - for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { - for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { - auto& populations = params_graph.nodes()[county_idx_i].property.base_sim.populations; - - // mobility coefficients have the same number of components as the contact matrices. - // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) - auto mobility_coeffs = MigrationCoefficientGroup(contact_locations_size, populations.numel()); - auto num_age_groups = - (size_t)params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups(); - commuting_weights = - (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); - - //commuters - auto working_population = 0.0; - auto min_commuter_age = mio::AgeGroup(2); - auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes - for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { - working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; - } - auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; - for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { - for (auto compartment : migrating_compartments) { - auto coeff_index = populations.get_flat_index({age, compartment}); - mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = - commuter_coeff_ij * commuting_weights[size_t(age)]; - } - } - - auto path = path_mobility[county_idx_i][county_idx_j]; - if (static_cast(path[0]) != county_idx_i || - static_cast(path[path.size() - 1]) != county_idx_j) - std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; - - //only add edges with mobility above thresholds for performance - if (commuter_coeff_ij > theshold_edges) { - params_graph.add_edge(county_idx_i, county_idx_j, std::move(mobility_coeffs), - travel_times(county_idx_i, county_idx_j), - path_mobility[county_idx_i][county_idx_j]); - // g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); - } - } - } - - return success(); -} - -// template -// class ParameterStudyDetailed : public ParameterStudy -// { -// public: -// /** -// * The type of simulation of a single node of the graph. -// */ -// using Simulation = S; -// /** -// * The Graph type that stores the parametes of the simulation. -// * This is the input of ParameterStudies. -// */ -// using ParametersGraphDetailed = mio::GraphDetailed>; -// /** -// * The Graph type that stores simulations and their results of each run. -// * This is the output of ParameterStudies for each run. -// */ -// using SimulationGraphDetailed = mio::GraphDetailed, mio::MigrationEdge>; - -// /** -// * create study for graph of compartment models. -// * @param graph graph of parameters -// * @param t0 start time of simulations -// * @param tmax end time of simulations -// * @param graph_sim_dt time step of graph simulation -// * @param num_runs number of runs -// */ -// ParameterStudyDetailed(const ParametersGraphDetailed& graph, double t0, double tmax, double graph_sim_dt, -// size_t num_runs) -// : ParameterStudy(graph, t0, tmax, graph_sim_dt, num_runs) -// , m_graph(graph) -// , m_num_runs(num_runs) -// , m_t0{t0} -// , m_tmax{tmax} -// , m_dt_graph_sim(graph_sim_dt) -// { -// } - -// /* -// * @brief Carry out all simulations in the parameter study. -// * Save memory and enable more runs by immediately processing and/or discarding the result. -// * The result processing function is called when a run is finished. It receives the result of the run -// * (a SimulationGraphDetailed object) and an ordered index. The values returned by the result processing function -// * are gathered and returned as a list. -// * This function is parallelized if memilio is configured with MEMILIO_ENABLE_MPI. -// * The MPI processes each contribute a share of the runs. The sample function and result processing function -// * are called in the same process that performs the run. The results returned by the result processing function are -// * gathered at the root process and returned as a list by the root in the same order as if the programm -// * were running sequentially. Processes other than the root return an empty list. -// * @param sample_graph Function that receives the ParametersGraph and returns a sampled copy. -// * @param result_processing_function Processing function for simulation results, e.g., output function. -// * @returns At the root process, a list of values per run that have been returned from the result processing function. -// * At all other processes, an empty list. -// * @tparam SampleGraphFunction Callable type, accepts instance of ParametersGraph. -// * @tparam HandleSimulationResultFunction Callable type, accepts instance of SimulationGraphDetailed and an index of type size_t. -// */ -// template -// std::vector> -// run(SampleGraphFunction sample_graph, HandleSimulationResultFunction result_processing_function) -// { -// int num_procs, rank; -// #ifdef MEMILIO_ENABLE_MPI -// MPI_Comm_size(mpi::get_world(), &num_procs); -// MPI_Comm_rank(mpi::get_world(), &rank); -// #else -// num_procs = 1; -// rank = 0; -// #endif - -// //The ParameterDistributions used for sampling parameters use thread_local_rng() -// //So we set our own RNG to be used. -// //Assume that sampling uses the thread_local_rng() and isn't multithreaded -// this->m_rng.synchronize(); -// thread_local_rng() = this->m_rng; - -// auto run_distribution = this->distribute_runs(m_num_runs, num_procs); -// auto start_run_idx = -// std::accumulate(run_distribution.begin(), run_distribution.begin() + size_t(rank), size_t(0)); -// auto end_run_idx = start_run_idx + run_distribution[size_t(rank)]; - -// std::vector> -// ensemble_result; -// ensemble_result.reserve(m_num_runs); - -// for (size_t run_idx = start_run_idx; run_idx < end_run_idx; run_idx++) { -// log(LogLevel::info, "ParameterStudies: run {}", run_idx); - -// //prepare rng for this run by setting the counter to the right offset -// //Add the old counter so that this call of run() produces different results -// //from the previous call -// auto run_rng_counter = -// this->m_rng.get_counter() + -// rng_totalsequence_counter(static_cast(run_idx), Counter(0)); -// thread_local_rng().set_counter(run_rng_counter); - -// //sample -// auto sim = create_sampled_sim(sample_graph); -// log(LogLevel::info, "ParameterStudies: Generated {} random numbers.", -// (thread_local_rng().get_counter() - run_rng_counter).get()); - -// //perform run -// sim.advance(m_tmax); - -// //handle result and store -// ensemble_result.emplace_back(result_processing_function(std::move(sim).get_graph(), run_idx)); -// } - -// //Set the counter of our RNG so that future calls of run() produce different parameters. -// this->m_rng.set_counter(this->m_rng.get_counter() + -// rng_totalsequence_counter(m_num_runs, Counter(0))); - -// #ifdef MEMILIO_ENABLE_MPI -// //gather results -// if (rank == 0) { -// for (int src_rank = 1; src_rank < num_procs; ++src_rank) { -// int bytes_size; -// MPI_Recv(&bytes_size, 1, MPI_INT, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); -// ByteStream bytes(bytes_size); -// MPI_Recv(bytes.data(), bytes.data_size(), MPI_BYTE, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE); - -// auto src_ensemble_results = deserialize_binary(bytes, Tag{}); -// if (!src_ensemble_results) { -// log_error("Error receiving ensemble results from rank {}.", src_rank); -// } -// std::copy(src_ensemble_results.value().begin(), src_ensemble_results.value().end(), -// std::back_inserter(ensemble_result)); -// } -// } -// else { -// auto bytes = serialize_binary(ensemble_result); -// auto bytes_size = int(bytes.data_size()); -// MPI_Send(&bytes_size, 1, MPI_INT, 0, 0, mpi::get_world()); -// MPI_Send(bytes.data(), bytes.data_size(), MPI_BYTE, 0, 0, mpi::get_world()); -// ensemble_result.clear(); //only return root process -// } -// #endif - -// return ensemble_result; -// } - -// private: -// //sample parameters and create simulation -// template -// mio::GraphSimulationDetailed create_sampled_sim(SampleGraphFunction sample_graph) -// { -// SimulationGraphDetailed sim_graph; - -// auto sampled_graph = sample_graph(m_graph); -// for (auto&& node : sampled_graph.nodes()) { -// sim_graph.add_node(node.id, node.stay_duration, node.property, node.mobility, m_t0, this->m_dt_integration); -// } -// for (auto&& edge : sampled_graph.edges()) { -// sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property.travel_time, edge.property); -// } -// return make_migration_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); -// } - -// private: -// // Stores Graph with the names and ranges of all parameters -// ParametersGraphDetailed m_graph; -// size_t m_num_runs; -// double m_t0; -// double m_tmax; -// double m_dt_graph_sim; -// }; - } // namespace mio #endif //METAPOPULATION_MOBILITY_DETAILED_H \ No newline at end of file diff --git a/cpp/models/ode_secirvvs/parameter_space.h b/cpp/models/ode_secirvvs/parameter_space.h index b29ac78e5e..17390a7ff3 100644 --- a/cpp/models/ode_secirvvs/parameter_space.h +++ b/cpp/models/ode_secirvvs/parameter_space.h @@ -21,6 +21,7 @@ #define ODESECIRVVS_PARAMETER_SPACE_H #include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/mobility/metapopulation_mobility_detailed.h" #include "memilio/utils/logging.h" #include "ode_secirvvs/model.h" #include "ode_secirvvs/infection_state.h" @@ -222,6 +223,84 @@ Graph, MigrationParameters> draw_sample(Graph, Migration return sampled_graph; } + +/** + * Draws samples for each model node in a graph. + * Some parameters are shared between nodes and only sampled once. + * @tparam FP floating point type, e.g., double + * @param graph Graph to be sampled. + * @param variant_high If true, use high value for infectiousness of variant. + * @return Graph with nodes and edges from the input graph sampled. + */ +template +ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_mask_transport) +{ + ExtendedGraph> sampled_graph; + + //sample global parameters + auto& shared_params_base_model = graph.nodes()[0].property.base_sim; + draw_sample_infection(shared_params_base_model); + auto& shared_contacts = shared_params_base_model.parameters.template get>(); + shared_contacts.draw_sample_dampings(); + auto& shared_dynamic_npis = shared_params_base_model.parameters.template get>(); + shared_dynamic_npis.draw_sample(); + + for (auto& params_node : graph.nodes()) { + auto& node_model_local = params_node.property.base_sim; + + //sample local parameters + draw_sample_demographics(params_node.property.base_sim); + + //copy global parameters + //save demographic parameters so they aren't overwritten + auto local_icu_capacity = node_model_local.parameters.template get>(); + auto local_tnt_capacity = node_model_local.parameters.template get>(); + auto local_holidays = node_model_local.parameters.template get>().get_school_holidays(); + auto local_daily_v1 = node_model_local.parameters.template get>(); + auto local_daily_v2 = node_model_local.parameters.template get>(); + node_model_local.parameters = shared_params_base_model.parameters; + node_model_local.parameters.template get>() = local_icu_capacity; + node_model_local.parameters.template get>() = local_tnt_capacity; + node_model_local.parameters.template get>().get_school_holidays() = local_holidays; + node_model_local.parameters.template get>() = local_daily_v1; + node_model_local.parameters.template get>() = local_daily_v2; + + node_model_local.parameters.template get>().make_matrix(); + node_model_local.apply_constraints(); + + // do the same for the mobility model. It should have the same parametrization as the base model but + // without vaccinations and the contact patterns are different. + auto& node_mobility_model = params_node.property.mobility_sim; + auto node_mobility_contacts = node_mobility_model.parameters.template get>(); + node_mobility_model.parameters = shared_params_base_model.parameters; + node_mobility_model.parameters.template get>() = node_mobility_contacts; + + // set vaccination parameters to zero + node_mobility_model.parameters.template get>().array().setConstant(0); + node_mobility_model.parameters.template get>().array().setConstant(0); + + // adjust the transmission factor for the mobility model based on the usage of masks + for (auto age = AgeGroup(0); age < node_mobility_model.parameters.get_num_groups(); ++age) { + node_mobility_model.parameters.template get>()[age] *= + fact_mask_transport; + } + node_mobility_model.apply_constraints(); + + sampled_graph.add_node(params_node.id, node_model_local, node_mobility_model, + params_node.property.stay_duration); + } + + for (auto& edge : graph.edges()) { + auto edge_params = edge.property.get_parameters(); + //no dynamic NPIs + //TODO: add switch to optionally enable dynamic NPIs to edges + sampled_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge_params, edge.property.travel_time, + edge.property.path); + } + + return sampled_graph; +} + } // namespace osecirvvs } // namespace mio diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index a7a36e7434..9f5914859c 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -385,6 +385,157 @@ void init_pop_cologne_szenario(mio::Graph, mio::Mi } } +/** + * @brief Sets the graph nodes for counties or districts. + * Reads the node ids which could refer to districts or counties and the epidemiological + * data from json files and creates one node for each id. Every node contains a model. + * @param[in] params Model Parameters that are used for every node. + * @param[in] start_date Start date for which the data should be read. + * @param[in] end_data End date for which the data should be read. + * @param[in] data_dir Directory that contains the data files. + * @param[in] population_data_path Path to json file containing the population data. + * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. + * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] read_func Function that reads input data for german counties and sets Model compartments. + * @param[in] node_func Function that returns the county ids. + * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. + * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. + * @param[in] tnt_capacity_factor Factor for test and trace capacity. + * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. + * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. + */ +template +mio::IOResult set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mio::Date end_date, + const fs::path& data_dir, const std::string& population_data_path, + const std::string& stay_times_data_path, bool is_node_for_county, + mio::ExtendedGraph>& params_graph, ReadFunction&& read_func, + NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, + FP scaling_factor_icu, FP tnt_capacity_factor, int num_days = 0, + bool export_time_series = false, bool rki_age_groups = true) +{ + + BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); + + BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county, rki_age_groups)); + std::vector> nodes(node_ids.size(), + mio::osecirvvs::Model(int(size_t(params.get_num_groups())))); + for (auto& node : nodes) { + node.parameters = params; + } + + BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), + num_days, export_time_series)); + + for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { + + auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; + + //local parameters + auto& tnt_value = nodes[node_idx].parameters.template get>(); + tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); + tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); + + //holiday periods + auto id = int(mio::regions::CountyId(node_ids[node_idx])); + auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); + auto& contacts = nodes[node_idx].parameters.template get>(); + contacts.get_school_holidays() = + std::vector>(holiday_periods.size()); + std::transform( + holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { + return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), + mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); + }); + + //uncertainty in populations + for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { + for (auto j = mio::Index::Compartments>(0); + j < mio::osecirvvs::Model::Compartments::Count; ++j) { + auto& compartment_value = nodes[node_idx].populations[{i, j}]; + compartment_value = + mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); + compartment_value.set_distribution(mio::ParameterDistributionUniform(0.9 * double(compartment_value), + 1.1 * double(compartment_value))); + } + } + + // Add mobility node + auto mobility_model = nodes[node_idx]; + mobility_model.populations.set_total(0); + + params_graph.add_node(1, nodes[node_idx], mobility_model, duration_stay((Eigen::Index)node_idx)); + } + return mio::success(); +} + +/** + * @brief Sets the graph edges. + * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. + * @param[in] travel_times_path Path to txt file containing the travel times between counties. + * @param[in] mobility_data_path Path to txt file containing the commuting matrices. + * @param[in] travelpath_path Path to txt file containing the paths between counties. + * @param[in, out] params_graph Graph whose nodes are set by the function. + * @param[in] migrating_compartments Compartments that commute. + * @param[in] contact_locations_size Number of contact locations. + * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. + */ +mio::IOResult +set_edges(const std::string& travel_times_dir, const std::string mobility_data_dir, const std::string& travel_path_dir, + mio::ExtendedGraph>& params_graph, + std::initializer_list& migrating_compartments, size_t contact_locations_size, + std::vector commuting_weights = std::vector{}, ScalarType theshold_edges = 4e-5) +{ + BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); + BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_dir)); + BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travel_path_dir)); + + for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { + for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { + auto& populations = params_graph.nodes()[county_idx_i].property.base_sim.populations; + + // mobility coefficients have the same number of components as the contact matrices. + // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) + auto mobility_coeffs = mio::MigrationCoefficientGroup(contact_locations_size, populations.numel()); + auto num_age_groups = + (size_t)params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups(); + commuting_weights = + (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); + + //commuters + auto working_population = 0.0; + auto min_commuter_age = mio::AgeGroup(2); + auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; + } + auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; + for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { + for (auto compartment : migrating_compartments) { + auto coeff_index = populations.get_flat_index({age, compartment}); + mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = + commuter_coeff_ij * commuting_weights[size_t(age)]; + } + } + + auto path = path_mobility[county_idx_i][county_idx_j]; + if (static_cast(path[0]) != county_idx_i || + static_cast(path[path.size() - 1]) != county_idx_j) + std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; + + //only add edges with mobility above thresholds for performance + if (commuter_coeff_ij > theshold_edges) { + params_graph.add_edge(county_idx_i, county_idx_j, std::move(mobility_coeffs), + travel_times(county_idx_i, county_idx_j), + path_mobility[county_idx_i][county_idx_j]); + // g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); + } + } + } + + return mio::success(); +} + /** * Create the input graph for the parameter study. * Reads files from the data directory. @@ -393,14 +544,14 @@ void init_pop_cologne_szenario(mio::Graph, mio::Mi * @param data_dir data directory. * @returns created graph or any io errors that happen during reading of the files. */ -mio::IOResult>> -get_graph(const int num_days, const std::string& data_dir, bool masks, bool ffp2, bool szenario_cologne, bool edges) +mio::IOResult>> get_graph(const mio::Date start_date, + const mio::Date end_date, const int num_days, + const std::string& data_dir, bool masks, + bool ffp2, bool szenario_cologne, bool edges) { mio::unused(num_days, masks, ffp2, szenario_cologne, edges); std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); - auto start_date = mio::Date(2022, 8, 1); - auto end_date = mio::Date(2022, 11, 1); // global parameters const int num_age_groups = 6; @@ -420,9 +571,7 @@ get_graph(const int num_days, const std::string& data_dir, bool masks, bool ffp2 const auto& read_function_nodes = mio::osecirvvs::read_input_data_county>; const auto& node_id_function = mio::get_node_ids; - auto set_nodes_status = mio::set_nodes, - mio::osecirvvs::ContactPatterns, mio::osecirvvs::Model, - mio::MigrationParameters, mio::osecirvvs::Parameters>( + auto set_nodes_status = set_nodes( params, start_date, end_date, data_dir, mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"), durations_dir, true, params_graph, read_function_nodes, node_id_function, scaling_factor_infected, scaling_factor_icu, @@ -433,231 +582,119 @@ get_graph(const int num_days, const std::string& data_dir, bool masks, bool ffp2 } // set edges - auto migrating_compartments = {mio::osecirvvs::InfectionState::SusceptibleNaive, - mio::osecirvvs::InfectionState::ExposedNaive, - mio::osecirvvs::InfectionState::InfectedNoSymptomsNaive, - mio::osecirvvs::InfectionState::InfectedSymptomsNaive, - mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity, - mio::osecirvvs::InfectionState::SusceptiblePartialImmunity, - mio::osecirvvs::InfectionState::ExposedPartialImmunity, - mio::osecirvvs::InfectionState::InfectedNoSymptomsPartialImmunity, - mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity, - mio::osecirvvs::InfectionState::ExposedImprovedImmunity, - mio::osecirvvs::InfectionState::InfectedNoSymptomsImprovedImmunity, - mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}; - auto set_edges_status = - mio::set_edges, mio::MigrationParameters, - mio::MigrationCoefficientGroup, mio::osecirvvs::InfectionState>( + if (edges) { + auto migrating_compartments = {mio::osecirvvs::InfectionState::SusceptibleNaive, + mio::osecirvvs::InfectionState::ExposedNaive, + mio::osecirvvs::InfectionState::InfectedNoSymptomsNaive, + mio::osecirvvs::InfectionState::InfectedSymptomsNaive, + mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity, + mio::osecirvvs::InfectionState::SusceptiblePartialImmunity, + mio::osecirvvs::InfectionState::ExposedPartialImmunity, + mio::osecirvvs::InfectionState::InfectedNoSymptomsPartialImmunity, + mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity, + mio::osecirvvs::InfectionState::ExposedImprovedImmunity, + mio::osecirvvs::InfectionState::InfectedNoSymptomsImprovedImmunity, + mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}; + auto set_edges_status = set_edges( travel_times_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), mio::path_join(data_dir, "mobility", "wegketten_ohne_komma.txt"), params_graph, migrating_compartments, contact_locations.size(), std::vector{0., 0., 1.0, 1.0, 0.33, 0., 0.}); - if (!set_edges_status) { - return set_edges_status.error(); + if (!set_edges_status) { + return set_edges_status.error(); + } } return params_graph; } -mio::IOResult run(const std::string data_dir) +mio::IOResult run(const std::string data_dir, std::string res_dir, const int num_runs = 2) { // mio::set_log_level(mio::LogLevel::critical); + auto start_date = mio::Date(2022, 8, 1); + auto end_date = mio::Date(2022, 11, 1); const auto num_days = 90; // const int num_runs = 12; - const bool masks = true; - const bool ffp2 = true; - const bool edges = true; + constexpr bool masks = true; + constexpr bool ffp2 = true; + const bool edges = true; + bool szenario_cologne = false; // wenn masks false und ffp2 true, dann error ausgeben - if (!masks && ffp2) { + if constexpr (!masks && ffp2) { mio::log_error("ffp2 only possible with masks"); } - bool szenario_cologne = false; - // auto params_graph = get_graph(num_days, masks); - BOOST_OUTCOME_TRY(auto&& created, get_graph(num_days, data_dir, masks, ffp2, szenario_cologne, edges)); + BOOST_OUTCOME_TRY(auto&& created, + get_graph(start_date, end_date, num_days, data_dir, masks, ffp2, szenario_cologne, edges)); auto params_graph = created; - // std::string res_dir = "/localdata1/code/memilio/results_paper/t90_f145_mask_" + std::to_string(masks); - - // res_dir += std::string(ffp2 ? "_ffp2" : "") + std::string(szenario_cologne ? "_cologne" : "") + - // std::string(!edges ? "_no_edges" : ""); - - // if (mio::mpi::is_root()) - // std::cout << "res_dir = " << res_dir << "\n"; - - // // check if boths dir exist, otherwise create them - // if (!fs::exists(res_dir)) { - // fs::create_directory(res_dir); - // } - // auto write_graph_status = mio::write_graph(params_graph, "/localdata1/code/memilio/save_graph"); - - // std::vector county_ids(params_graph.nodes().size()); - // std::transform(params_graph.nodes().begin(), params_graph.nodes().end(), county_ids.begin(), [](auto& n) { - // return n.id; - // }); - - // // parameter study - // auto parameter_study = mio::ParameterStudy>>( - // params_graph, 0.0, num_days, 0.01, num_runs); - // if (mio::mpi::is_root()) { - // printf("Seeds: "); - // for (auto s : parameter_study.get_rng().get_seeds()) { - // printf("%u, ", s); - // } - // printf("\n"); - // } - // auto save_single_run_result = mio::IOResult(mio::success()); - // auto ensemble = parameter_study.run( - // [&](auto&& graph) { - // return draw_sample(graph, false); - // }, - // [&](auto results_graph, auto&& run_idx) { - // auto interpolated_result = mio::interpolate_simulation_result(results_graph); - - // auto params = std::vector(); - // params.reserve(results_graph.nodes().size()); - // std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), - // [](auto&& node) { - // return node.property.get_simulation().get_model(); - // }); - - // auto tp_mob_node = results_graph.nodes()[0].node_pt.get_simulation().get_flows().get_num_time_points(); - - // // transmission mobility models - // const std::string results_path_mb = res_dir + "/flows_mb"; - // if (!fs::exists(results_path_mb)) { - // fs::create_directory(results_path_mb); - // } - // const std::string results_filename = - // results_path_mb + "/transmission_mobility_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile(results_filename); - - // outfile << "Node_from Node_to Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.edges().size(); ++i) { - // auto& edge = results_graph.edges()[i]; - // outfile << edge.start_node_idx << " " << edge.end_node_idx << " "; - // const auto size_vec = edge.infecions_mobility.size(); - // for (size_t j = 0; j < size_vec; ++j) { - // outfile << edge.infecions_mobility[j] << " "; - // } - // outfile << "\n"; - // } - // outfile.close(); - - // // transmission commuter in local models - // const std::string results_filename_local_transmission = - // results_path_mb + "/transmission_local_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile_transmission_local(results_filename_local_transmission); - - // outfile_transmission_local << "Node_from Node_to Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.edges().size(); ++i) { - // auto& edge = results_graph.edges()[i]; - // outfile_transmission_local << edge.start_node_idx << " " << edge.end_node_idx << " "; - // const auto size_vec = edge.infecions_local_model.size(); - // for (size_t j = 0; j < size_vec; ++j) { - // outfile_transmission_local << edge.infecions_local_model[j] << " "; - // } - // outfile_transmission_local << "\n"; - // } - // outfile_transmission_local.close(); - - // // symptomatic infections commuter in mobility models - // const std::string results_filename_mobility_symp = - // results_path_mb + "/symptomps_mobility_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile_symp_mobility(results_filename_mobility_symp); - - // outfile_symp_mobility << "Node_from Node_to Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.edges().size(); ++i) { - // auto& edge = results_graph.edges()[i]; - // outfile_symp_mobility << edge.start_node_idx << " " << edge.end_node_idx << " "; - // const auto size_vec = edge.infecions_symptomatic_mobility.size(); - // for (size_t j = 0; j < size_vec; ++j) { - // outfile_symp_mobility << edge.infecions_symptomatic_mobility[j] << " "; - // } - // outfile_symp_mobility << "\n"; - // } - // outfile_symp_mobility.close(); - - // // symptomatic infections commuter in local models - // const std::string results_filename_local_symp = - // results_path_mb + "/symptomps_local_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile_symp_local(results_filename_local_symp); - - // outfile_symp_local << "Node_from Node_to Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.edges().size(); ++i) { - // auto& edge = results_graph.edges()[i]; - // outfile_symp_local << edge.start_node_idx << " " << edge.end_node_idx << " "; - // const auto size_vec = edge.infecions_symptomatic_local_model.size(); - // for (size_t j = 0; j < size_vec; ++j) { - // outfile_symp_local << edge.infecions_symptomatic_local_model[j] << " "; - // } - // outfile_symp_local << "\n"; - // } - // outfile_symp_local.close(); - - // // symptomatic infections total in local models - // const std::string results_filename_total_symp = - // results_path_mb + "/total_symptomps_local_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile_symp_local_total(results_filename_total_symp); - // std::vector indx_symp = {20, 36, 56, 73, 89, 109, 126, 142, 162, - // 179, 195, 215, 232, 248, 268, 285, 301}; - // outfile_symp_local_total << "Node Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.nodes().size(); ++i) { - // auto& node = results_graph.nodes()[i]; - // auto flows = mio::interpolate_simulation_result(node.property.get_simulation().get_flows()); - // outfile_symp_local_total << i << " "; - // for (auto idx = 0; idx < flows.get_num_time_points(); ++idx) { - // auto flows_step = flows.get_value(idx); - // auto sum_infections = - // std::accumulate(indx_symp.begin(), indx_symp.end(), 0.0, [&flows_step](double sum, int i) { - // return sum + flows_step(i); - // }); - // outfile_symp_local_total << sum_infections << " "; - // } - // outfile_symp_local_total << "\n"; - // } - // outfile_symp_local_total.close(); - - // // transmission infections total in local models - // const std::string results_filename_total_transmission = - // results_path_mb + "/total_transmission_local_run_" + std::to_string(run_idx) + ".txt"; - // std::ofstream outfile_tra_local_total(results_filename_total_transmission); - // std::vector indx_infections = {0, 33, 17, 53, 86, 70, 106, 139, 123, - // 159, 192, 176, 212, 245, 229, 265, 298, 282}; - // outfile_tra_local_total << "Node Infections_mobility_node \n"; - // for (size_t i = 0; i < results_graph.nodes().size(); ++i) { - // auto& node = results_graph.nodes()[i]; - // auto flows = mio::interpolate_simulation_result(node.property.get_simulation().get_flows()); - // outfile_tra_local_total << i << " "; - // for (auto idx = 0; idx < flows.get_num_time_points(); ++idx) { - // auto flows_step = flows.get_value(idx); - // auto sum_infections = std::accumulate(indx_infections.begin(), indx_infections.end(), 0.0, - // [&flows_step](double sum, int i) { - // return sum + flows_step(i); - // }); - // outfile_tra_local_total << sum_infections << " "; - // } - // outfile_tra_local_total << "\n"; - // } - // outfile_tra_local_total.close(); - - // std::cout << "Run " << run_idx << " complete." << std::endl; - - // return std::make_pair(interpolated_result, params); - // }); - - // if (ensemble.size() > 0) { - // auto ensemble_results = std::vector>>{}; - // ensemble_results.reserve(ensemble.size()); - // auto ensemble_params = std::vector>>{}; - // ensemble_params.reserve(ensemble.size()); - // for (auto&& run : ensemble) { - // ensemble_results.emplace_back(std::move(run.first)); - // ensemble_params.emplace_back(std::move(run.second)); - // } - // BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); - // } + res_dir += "masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + + std::string(szenario_cologne ? "_cologne" : "") + std::string(!edges ? "_no_edges" : ""); + + if (mio::mpi::is_root()) + std::cout << "res_dir = " << res_dir << "\n"; + + // check if res_dir dir exist, otherwise create + if (!fs::exists(res_dir)) { + fs::create_directory(res_dir); + } + + std::vector county_ids(params_graph.nodes().size()); + std::transform(params_graph.nodes().begin(), params_graph.nodes().end(), county_ids.begin(), [](auto& n) { + return n.id; + }); + + mio::unused(county_ids); + // parameter study + auto parameter_study = mio::ParameterStudy< + mio::osecirvvs::Simulation>>, + mio::ExtendedGraph>, + mio::ExtendedGraph>>>(params_graph, 0.0, num_days, + 0.01, num_runs); + if (mio::mpi::is_root()) { + printf("Seeds: "); + for (auto s : parameter_study.get_rng().get_seeds()) { + printf("%u, ", s); + } + printf("\n"); + } + auto save_single_run_result = mio::IOResult(mio::success()); + auto ensemble = parameter_study.run( + [&](auto&& graph) { + return draw_sample(graph, 1.0); + }, + [&](auto results_graph, auto&& run_idx) { + std::vector> interpolated_result; + interpolated_result.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), + std::back_inserter(interpolated_result), [](auto& n) { + return interpolate_simulation_result(n.property.base_sim.get_result()); + }); + + auto params = std::vector>(); + params.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), + [](auto&& node) { + return node.property.base_sim.get_model(); + }); + + std::cout << "Run " << run_idx << " complete." << std::endl; + + return std::make_pair(interpolated_result, params); + }); + + if (ensemble.size() > 0) { + auto ensemble_results = std::vector>>{}; + ensemble_results.reserve(ensemble.size()); + auto ensemble_params = std::vector>>{}; + ensemble_params.reserve(ensemble.size()); + for (auto&& run : ensemble) { + ensemble_results.emplace_back(std::move(run.first)); + ensemble_params.emplace_back(std::move(run.second)); + } + BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); + } return mio::success(); } @@ -667,9 +704,11 @@ int main() mio::set_log_level(mio::LogLevel::warn); mio::mpi::init(); - const std::string data_dir = "/localdata1/code/memilio/data"; + const std::string memilio_dir = "/localdata1/pure/memilio"; + const std::string data_dir = mio::path_join(memilio_dir, "data"); //"/localdata1/code/memilio/data"; + std::string results_dir = mio::path_join(memilio_dir, "results"); - auto result = run(data_dir); + auto result = run(data_dir, results_dir); if (!result) { printf("%s\n", result.error().formatted_message().c_str()); mio::mpi::finalize(); From 67e15195bdc6f25e6d98e16f2d359d96c5ac19e3 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:19:16 +0200 Subject: [PATCH 10/26] adjust examples, complete rework of new graph sim --- cpp/examples/ode_seir_detailed_mobility.cpp | 8 +- .../metapopulation_mobility_detailed.h | 460 ++++++++---------- cpp/models/ode_secirvvs/model.h | 5 +- .../2022_omicron_late_phase_mobility.cpp | 45 +- 4 files changed, 245 insertions(+), 273 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index 3dcbbf9a35..faadefdb8f 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -23,6 +23,7 @@ #include "memilio/mobility/metapopulation_mobility_instant.h" #include "memilio/mobility/metapopulation_mobility_detailed.h" #include "memilio/compartments/simulation.h" +#include "memilio/compartments/flow_simulation.h" #include "memilio/data/analyze_result.h" int main() @@ -57,11 +58,12 @@ int main() model_group1.populations[{mio::AgeGroup(0), mio::oseir::InfectionState::Susceptible}] = 9990; model_group1.populations[{mio::AgeGroup(0), mio::oseir::InfectionState::Exposed}] = 10; - auto sim1 = mio::Simulation>(model_group1, t0, dt); - auto sim2 = mio::Simulation>(model_group2, t0, dt); + auto sim1 = mio::FlowSimulation>(model_group1, t0, dt); + auto sim2 = mio::FlowSimulation>(model_group2, t0, dt); double stay_time_1 = 0.3; double stay_time_2 = 0.3; - mio::ExtendedGraph>> g; + // mio::ExtendedGraph>> g; + mio::ExtendedGraph>> g; g.add_node(1, sim1, sim2, stay_time_1); g.add_node(2, sim1, sim2, stay_time_2); diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 49a31a1b19..269b4d25ab 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -24,6 +24,7 @@ #include "memilio/epidemiology/simulation_day.h" #include "memilio/mobility/graph_simulation.h" #include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/utils/logging.h" #include "memilio/utils/time_series.h" #include "memilio/math/eigen.h" #include "memilio/math/eigen_util.h" @@ -41,6 +42,7 @@ #include "boost/filesystem.hpp" #include +#include #include namespace mio @@ -106,144 +108,133 @@ auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref -void move_migrated(Eigen::Ref> migrated, Eigen::Ref> results_from, - Eigen::Ref> results_to) +void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-10) { - // before moving the commuters, we need to look for negative values in migrated and correct them. - const auto group = 6; - const auto num_comparts = results_to.size() / group; - - // check for negative values in migrated - for (Eigen::Index j = 0; j < migrated.size(); ++j) { - if (migrated(j) < -1e-8) { - std::cout << "Negative Value in migration detected. With value" << migrated(j) << "\n"; - auto curr_age_group = int(j / num_comparts); - auto indx_begin = curr_age_group * num_comparts; - auto indx_end = (curr_age_group + 1) * num_comparts; - // calculate max index in indx boundaries - Eigen::Index max_index = indx_begin; - for (Eigen::Index i = indx_begin; i < indx_end; ++i) { - if (migrated(i) > migrated(max_index)) { - max_index = i; - } + // before moving the commuters, we need to look for negative values in vec and correct them. + const size_t num_comparts = vec.size() / num_age_groups; + + // check for negative values in vec + while (vec.minCoeff() < tolerance) { + Eigen::Index min_index; + const FP min_value = vec.minCoeff(&min_index); + + const auto curr_age_group = min_index / num_comparts; + const auto indx_begin = curr_age_group * num_comparts; + const auto indx_end = indx_begin + num_comparts; + + auto max_group_indx = indx_begin; + for (auto i = indx_begin; i < indx_end; ++i) { + if (vec(i) > vec(max_group_indx)) { + max_group_indx = i; } - // we assume that the solution from migrated is bettter because there is contact with other nodes - migrated(max_index) = migrated(max_index) + migrated(j); - migrated(j) = migrated(j) - migrated(j); } - } - - // calc sum of migrated and from - auto sum_migrated = migrated.sum(); - auto sum_from = results_from.sum(); - if (std::abs(sum_migrated - sum_from) < 1e-8) { - results_from = migrated; - } - else { - Eigen::VectorXd remaining_after_return = (results_from - migrated).eval(); - // auto remaining_after_return_as_vector = std::vector( - // remaining_after_return.data(), remaining_after_return.data() + remaining_after_return.size()); - for (Eigen::Index j = 0; j < results_to.size(); ++j) { - if (remaining_after_return(j) < -1e-8) { - auto curr_age_group = int(j / num_comparts); - auto indx_begin = curr_age_group * num_comparts; - auto indx_end = (curr_age_group + 1) * num_comparts; - // calculate max index in indx boundaries - Eigen::Index max_index = indx_begin; - for (Eigen::Index i = indx_begin; i < indx_end; ++i) { - if (remaining_after_return(i) > remaining_after_return(max_index)) { - max_index = i; - } - } - // its possible that the max value in the boundaries is not enough to fill the negative value. - // Therefore we have to find multiple max values - while (remaining_after_return(max_index) + remaining_after_return(j) < -1e-10) { + // Correct the negative value + vec(min_index) = 0; + vec(max_group_indx) += min_value; - // calculate sum between indx_begin and indx_end - double result_from_sum_group = 0; - double result_migration_sum_group = 0; - for (Eigen::Index i = indx_begin; i < indx_end; ++i) { - result_from_sum_group += results_from(i); - result_migration_sum_group += migrated(i); - } - auto diff = result_from_sum_group - result_migration_sum_group; - if (diff < -1e-8) { - std::cout << "Sum of results_from is smaller than sum of migrated. Diff is " - << result_from_sum_group - result_migration_sum_group << "\n"; - // transfer values from migrated to results_from - for (Eigen::Index i = indx_begin; i < indx_end; ++i) { - results_from(i) = migrated(i); - } - } + std::cout << "Negative value in vector detected and corrected. Value: " << min_value << "\n"; + } +} - results_from(j) = results_from(j) + remaining_after_return(max_index); - results_from(max_index) = results_from(max_index) - remaining_after_return(max_index); - remaining_after_return = (results_from - migrated).eval(); +template +Eigen::Index find_time_index(Sim& simulation, FP t, bool create_new_tp) +{ + auto& results = simulation.get_result(); + auto& flows = simulation.get_flows(); + if (results.get_num_time_points() != flows.get_num_time_points()) { + log_error("Number of time points in results " + std::to_string(results.get_num_time_points()) + " and flows " + + std::to_string(flows.get_num_time_points()) + " do not match in find_time_index."); + } + Eigen::Index t_indx = results.get_num_time_points() - 1; + for (; t_indx >= 0; --t_indx) { + if (std::abs(results.get_time(t_indx) - t) < 1e-10) { + break; + } + } - max_index = indx_begin; - for (Eigen::Index i = indx_begin; i < indx_end; ++i) { - if (remaining_after_return(i) > remaining_after_return(max_index)) { - max_index = i; - } - } - if (max_index == indx_begin && remaining_after_return(max_index) == 0) { - std::cout << "Fixing negative Value in migration not possible." - << "\n"; - } - } + if (t_indx < 0 && !create_new_tp) { + log_error("Time point " + std::to_string(t) + " not found in find_time_index. Lates time point is " + + std::to_string(results.get_last_time())); + } - // we assume that the solution from migrated is bettter because there is contact with other nodes - results_from(j) = results_from(j) - remaining_after_return(j); - results_from(max_index) = results_from(max_index) + remaining_after_return(j); - remaining_after_return = (results_from - migrated).eval(); - } - } + if (t_indx < 0 && results.get_last_time() < t && create_new_tp) { + // if we allow to create a new time point, we initialize the compartments with zero + // the flows are accumulated. Therefore, we can just copy the last value. + Eigen::VectorXd results_vec = Eigen::VectorXd::Zero(results.get_last_value().size()); + results.add_time_point(t, results_vec); + flows.add_time_point(t, flows.get_last_value()); + t_indx = results.get_num_time_points() - 1; } - results_from -= migrated; - results_to += migrated; + return t_indx; } template class MobilityFunctions { public: - void init_mobility(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void init_mobility(FP t, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) { + const auto t_indx_start_mobility_sim_from = find_time_index(from_sim, t, false); + + // initialize the number of commuters at the start of the mobility + auto results_from = from_sim.get_result(); edge.get_migrated().add_time_point( - t, (from_sim.get_result().get_last_value().array() * + t, (results_from.get_value(t_indx_start_mobility_sim_from).array() * edge.get_parameters().get_coefficients().get_matrix_at(t).array() * - get_migration_factors(from_sim, t, from_sim.get_result().get_last_value()).array()) + get_migration_factors(from_sim, t, results_from.get_value(t_indx_start_mobility_sim_from)).array()) .matrix()); - edge.get_return_times().add_time_point(t + dt); - move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), - to_sim.get_result().get_last_value()); + edge.get_return_times().add_time_point(t); + + // move them to the starting mobility model + // if the simulation we are adding the commuters to is not having the same time point as the current time point, + // we need to add a new time point to the simulation. + const auto t_indx_start_mobility_sim_to = find_time_index(to_sim, t, true); + to_sim.get_result().get_value(t_indx_start_mobility_sim_to) += edge.get_migrated().get_last_value(); + from_sim.get_result().get_last_value() -= edge.get_migrated().get_last_value(); } - void update_and_move(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void move_migrated(FP t, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) { - auto& integrator_node = from_sim.get_integrator(); - update_status_migrated(edge.get_migrated().get_last_value(), from_sim, integrator_node, - from_sim.get_result().get_last_value(), t, dt); - move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), - to_sim.get_result().get_last_value()); + // When moving from one regional entity/model to another, we need to update the local population. + // check_negative_values_vec needs to be called once since its checks for negative values and corrects them. + const size_t num_age_groups = static_cast(from_sim.get_model().parameters.get_num_groups()); + check_negative_values_vec(edge.get_migrated().get_last_value(), num_age_groups); + const auto t_indx_sim_to_arrival = find_time_index(to_sim, t, true); + from_sim.get_result().get_last_value() -= edge.get_migrated().get_last_value(); + to_sim.get_result().get_value(t_indx_sim_to_arrival) += edge.get_migrated().get_last_value(); + + // check each result for negative values and correct them if necessary + check_negative_values_vec(from_sim.get_result().get_last_value(), num_age_groups); + check_negative_values_vec(to_sim.get_result().get_value(t_indx_sim_to_arrival), num_age_groups); } - void update_only(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& from_sim) + void update_commuters(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& sim, bool is_mobility_model) { - auto& integrator_node = from_sim.get_integrator(); - update_status_migrated(edge.get_migrated().get_last_value(), from_sim, integrator_node, - from_sim.get_result().get_last_value(), t, dt); - move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), - from_sim.get_result().get_last_value()); + auto& integrator_node = sim.get_integrator(); + const auto t_indx_start_mobility_sim = find_time_index(sim, t, true); + Eigen::VectorXd flows = Eigen::VectorXd::Zero(sim.get_flows().get_last_value().size()); + update_status_migrated(edge.get_migrated().get_last_value(), sim, integrator_node, + sim.get_result().get_value(t_indx_start_mobility_sim), t, dt, flows); + + // if the simulation is holding a mobility model, we need to update the mobility model as well + // Therefore, we check if the time point already exists in the mobility model and create a new one if necessary. + // Next, we build the population in the mobility model based on the commuters + if (is_mobility_model) { + const auto t_indx_mobility_model = find_time_index(sim, t + dt, true); + if (t_indx_mobility_model != sim.get_result().get_num_time_points() - 1) { + log_error("Time point " + std::to_string(t + dt) + + " not the lastest in update_commuters. Latest time point is " + + std::to_string(sim.get_result().get_last_time())); + } + sim.get_result().get_value(t_indx_mobility_model) += edge.get_migrated().get_last_value(); + sim.get_flows().get_value(t_indx_mobility_model) += flows; + } } - void move_and_delete(ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void delete_migrated(ExtendedMigrationEdge& edge) { - move_migrated(edge.get_migrated().get_last_value(), from_sim.get_result().get_last_value(), - to_sim.get_result().get_last_value()); - for (Eigen::Index i = edge.get_return_times().get_num_time_points() - 1; i >= 0; --i) { edge.get_migrated().remove_time_point(i); edge.get_return_times().remove_time_point(i); @@ -306,11 +297,11 @@ class ScheduleManager const double traveltime_per_region = std::max(0.01, round_nth_decimal(e.property.travel_time / e.property.path.size(), 2)); - // Calculate the start time for mobility, ensuring it is not negative + // Calculate the start time for mobility, ensuring it greater or equal to 0.01 const double start_mobility = - std::max(0.0, round_nth_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - - graph.nodes()[e.end_node_idx].property.stay_duration, - 2)); + std::max(0.01, round_nth_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - + graph.nodes()[e.end_node_idx].property.stay_duration, + 2)); // Calculate the arrival time at the destination node const double arrive_at = start_mobility + traveltime_per_region * e.property.path.size(); @@ -326,9 +317,9 @@ class ScheduleManager }; // Indices for schedule filling - size_t start_idx = static_cast((start_mobility + epsilon) * 100); - size_t arrive_idx = static_cast((arrive_at + epsilon) * 100); - size_t stay_end_idx = timesteps - (arrive_idx - start_idx); + const size_t start_idx = static_cast((start_mobility + epsilon) * 100); + const size_t arrive_idx = static_cast((arrive_at + epsilon) * 100); + const size_t stay_end_idx = timesteps - (arrive_idx - start_idx); // Fill the schedule up to the start of mobility with the start node index fill_schedule(0, start_idx, e.start_node_idx); @@ -357,7 +348,7 @@ class ScheduleManager // Ensure there is at least one true value if (first_true != is_mobility_node.end() && last_true != is_mobility_node.rend()) { size_t first_index = std::distance(is_mobility_node.begin(), first_true); - size_t count_true = std::count(is_mobility_node.begin(), is_mobility_node.end(), true); + size_t count_true = arrive_idx - start_idx; // Create a reversed path segment for the return trip std::vector path_reversed(tmp_schedule.begin() + first_index, @@ -609,12 +600,17 @@ class GraphSimulationExtended : public GraphSimulationBase size_t indx_schedule = 0; while (t_begin + 1 > this->m_t + 1e-10) { - advance_edges(indx_schedule); - - // first we integrate the nodes in time. Afterwards the update on the edges is done. - // We start with the edges since the values for t0 are given. + // the graph simulation is structured in 3 steps: + // 1. move indivudals if necessary + // 2. integrate the local nodes + // 3. update the status of the commuter and use the obtained values to update the mobility models. + move_commuters(indx_schedule); advance_local_nodes(indx_schedule); - advance_mobility_nodes(indx_schedule); + update_status_commuters(indx_schedule); + + // in the last time step we have to move the individuals back to their local model and + // delete the commuters time series + handle_last_time_step(indx_schedule); indx_schedule++; this->m_t += min_dt; @@ -633,53 +629,55 @@ class GraphSimulationExtended : public GraphSimulationBase ScheduleManager::Schedule schedules; const double epsilon = 1e-10; - void advance_edges(size_t indx_schedule) + template + ScalarType calculate_next_dt(size_t edge_indx, size_t indx_schedule, const Edge& e) + { + auto current_node_indx = schedules.schedule_edges[edge_indx][indx_schedule]; + bool in_mobility_node = schedules.mobility_schedule_edges[edge_indx][indx_schedule]; + + // determine dt, which is equal to the last integration/synchronization point in the current node + auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; + if (in_mobility_node) + integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; + // search the index of indx_schedule in the integrator schedule + const size_t indx_current = std::distance( + integrator_schedule_row.begin(), + std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); + + if (integrator_schedule_row[indx_current] != indx_schedule) + throw std::runtime_error("Error in schedule."); + + ScalarType dt_mobility; + if (indx_current == integrator_schedule_row.size() - 1) { + dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); + if (dt_mobility < 0.01) + dt_mobility = 0.01; + } + else { + dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current + 1]) - + static_cast(integrator_schedule_row[indx_current])) / + 100 + + epsilon, + 2); + } + + return dt_mobility; + } + + void move_commuters(size_t indx_schedule) { for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { auto& e = this->m_graph.edges()[edge_indx]; - // first mobility activity + // start mobility by initializing the number of commuters and move to initial mobility model if (indx_schedule == schedules.first_mobility[edge_indx]) { auto& node_from = this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; auto& node_to = this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; - // m_edge_func(m_t, 0.0, e.property, node_from, node_to, 0); - m_mobility_functions.init_mobility(this->m_t, 0.0, e.property, node_from, node_to); + m_mobility_functions.init_mobility(this->m_t, e.property, node_from, node_to); } - // next mobility activity else if (indx_schedule > schedules.first_mobility[edge_indx]) { - auto current_node_indx = schedules.schedule_edges[edge_indx][indx_schedule]; - bool in_mobility_node = schedules.mobility_schedule_edges[edge_indx][indx_schedule]; - - // determine dt, which is equal to the last integration/syncronization point in the current node - auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; - if (in_mobility_node) - integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; - // search the index of indx_schedule in the integrator schedule - const size_t indx_current = std::distance( - integrator_schedule_row.begin(), - std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); - - if (integrator_schedule_row[indx_current] != indx_schedule) - std::cout << "Error in schedule." - << "\n"; - - ScalarType dt_mobility; - if (indx_current == 0) { - dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); - if (dt_mobility < 0.01) - dt_mobility = 0.01; - } - else { - dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current]) - - static_cast(integrator_schedule_row[indx_current - 1])) / - 100 + - epsilon, - 2); - } - - // We have two cases. Either, we want to send the individuals to the next node, or we just want - // to update their state since a syncronization step is necessary in the current node. + // send the individuals to the next node if ((schedules.schedule_edges[edge_indx][indx_schedule] != schedules.schedule_edges[edge_indx][indx_schedule - 1]) || (schedules.mobility_schedule_edges[edge_indx][indx_schedule] != @@ -697,71 +695,27 @@ class GraphSimulationExtended : public GraphSimulationBase : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] .property.base_sim; - if (indx_schedule < schedules.mobility_schedule_edges[edge_indx].size() - 1) { - // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 1); - m_mobility_functions.update_and_move(this->m_t, dt_mobility, e.property, node_from, node_to); - } - } - else { - auto& node_from = - schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] - .property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] - .property.base_sim; - - assert(node_from.get_result().get_last_value() == - (schedules.mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] - .property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] - .property.base_sim) - .get_result() - .get_last_value()); - // m_edge_func(m_t, dt_mobility, e.property, node_from, node_to, 3); - m_mobility_functions.update_only(this->m_t, dt_mobility, e.property, node_from); + m_mobility_functions.move_migrated(this->m_t, e.property, node_from, node_to); } } } - // in the last time step we have to move the individuals back to their local model - if (indx_schedule == 99) { - auto edge_index = 0; - for (auto& e : this->m_graph.edges()) { - auto current_node_indx = schedules.schedule_edges[edge_index][indx_schedule]; - bool in_mobility_node = schedules.mobility_schedule_edges[edge_index][indx_schedule]; - - // determine dt, which is equal to the last integration/syncronization point in the current node - auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; - if (in_mobility_node) - integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; - // search the index of indx_schedule in the integrator schedule - const size_t indx_current = std::distance( - integrator_schedule_row.begin(), - std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); - - ScalarType dt_mobility; - if (indx_current == 0) { - dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); - if (dt_mobility < 0.01) - dt_mobility = 0.01; - } - else { - dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current]) - - static_cast(integrator_schedule_row[indx_current - 1])) / - 100 + - epsilon, - 2); - } - - auto& node_from = - this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.mobility_sim; - - auto& node_to = - this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.base_sim; + } - m_mobility_functions.move_and_delete(e.property, node_from, node_to); - edge_index++; - } + void update_status_commuters(size_t indx_schedule) + { + for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { + auto& e = this->m_graph.edges()[edge_indx]; + auto next_dt = calculate_next_dt(edge_indx, indx_schedule, e); + auto& node_to = + schedules.mobility_schedule_edges[edge_indx][indx_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.base_sim; + + // TODO: Muss ich hier node_from oder node_to verwenden? Evtl ist die aktuelle config dann falsch. + // Aber sieht gut aus denke ich. Das ja getrennt von exchanges. + + m_mobility_functions.update_commuters(this->m_t, next_dt, e.property, node_to, + schedules.mobility_schedule_edges[edge_indx][indx_schedule]); } } @@ -780,44 +734,36 @@ class GraphSimulationExtended : public GraphSimulationBase const ScalarType next_dt = round_nth_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon, 2); n.property.base_sim.advance(this->m_t + next_dt); - // m_node_func(this->m_t, next_dt, n.property.base_sim); + // check if last value contains negative values or nan values + if (n.property.base_sim.get_result().get_last_value().minCoeff() < -1e-7 || + std::isnan(n.property.base_sim.get_result().get_last_value().sum())) { + auto last_value_as_vec = + std::vector(n.property.base_sim.get_result().get_last_value().data(), + n.property.base_sim.get_result().get_last_value().data() + + n.property.base_sim.get_result().get_last_value().size()); + std::cout << "Negative Value in local node detected." + << "\n"; + } } } - void advance_mobility_nodes(size_t indx_schedule) + void handle_last_time_step(int indx_schedule) { - for (const size_t& n_indx : schedules.nodes_mobility_m[indx_schedule]) { - auto& n = this->m_graph.nodes()[n_indx]; - // determine in which index of mobility_int_schedule we are - const size_t indx_current = - std::distance(schedules.mobility_int_schedule[n_indx].begin(), - std::lower_bound(schedules.mobility_int_schedule[n_indx].begin(), - schedules.mobility_int_schedule[n_indx].end(), indx_schedule)); - // determine the next time step - const size_t val_next = (indx_current == schedules.mobility_int_schedule[n_indx].size() - 1) - ? 100 - : schedules.mobility_int_schedule[n_indx][indx_current + 1]; - const ScalarType next_dt = - round_nth_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon, 2); + if (indx_schedule == 99) { + auto edge_index = 0; + for (auto& e : this->m_graph.edges()) { + auto& node_from = + this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.mobility_sim; + auto& node_to = + this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.base_sim; - // get all time points from the last integration step - auto& last_time_point = n.property.mobility_sim.get_result().get_time( - n.property.mobility_sim.get_result().get_num_time_points() - 1); - // if the last time point is not within an interval of 1-e10 from t, then set the last time point to m_t - if (std::fabs(last_time_point - this->m_t) > 1e-10) { - n.property.mobility_sim.get_result().get_last_time() = this->m_t; - } - // only advance in time if there are individuals in the mobility model - if (n.property.mobility_sim.get_result().get_last_value().sum() > 1e-8) { - n.property.mobility_sim.advance(this->m_t + next_dt); - } - Eigen::Index indx_min; - while (n.property.mobility_sim.get_result().get_last_value().minCoeff(&indx_min) < -1e-7) { - Eigen::Index indx_max; - n.property.mobility_sim.get_result().get_last_value().maxCoeff(&indx_max); - n.property.mobility_sim.get_result().get_last_value()[indx_max] -= - n.property.mobility_sim.get_result().get_last_value()[indx_min]; - n.property.mobility_sim.get_result().get_last_value()[indx_min] = 0; + if (schedules.schedule_edges[edge_index][indx_schedule] != e.start_node_idx) + log_error("Last node is not the start node in edge " + std::to_string(edge_index) + " at time " + + std::to_string(indx_schedule)); + // move the individuals back to the local model + m_mobility_functions.move_migrated(this->m_t + 0.01, e.property, node_from, node_to); + m_mobility_functions.delete_migrated(e.property); + edge_index++; } } } @@ -848,7 +794,8 @@ make_migration_sim(FP t0, FP dt, Graph, ExtendedMigrat template ::value>> void update_status_migrated(Eigen::Ref::Vector> migrated, Sim& sim, mio::IntegratorCore& integrator, - Eigen::Ref::Vector> total, FP t, FP dt) + Eigen::Ref::Vector> total, FP t, FP dt, + Eigen::VectorXd& flows) { auto y0 = migrated.eval(); auto y1 = migrated; @@ -857,9 +804,8 @@ void update_status_migrated(Eigen::Ref::Vector> migrated sim.get_model().get_derivatives(total, y, t_, dydt); }, y0, t, dt, y1); - auto flows_model = sim.get_model().get_flow_values(); - flows_model *= dt; - sim.get_model().set_flow_values(flows_model); + flows = sim.get_model().get_flow_values(); + flows *= dt; } } // namespace mio diff --git a/cpp/models/ode_secirvvs/model.h b/cpp/models/ode_secirvvs/model.h index cff82bf5f6..d9c36a9ab3 100644 --- a/cpp/models/ode_secirvvs/model.h +++ b/cpp/models/ode_secirvvs/model.h @@ -243,7 +243,10 @@ class Model pop[INSPICj] + pop[ISyPICj] + pop[SIIj] + pop[EIIj] + pop[INSIIj] + pop[ISyIIj] + pop[ISevIIj] + pop[ICrIIj] + pop[INSIICj] + pop[ISyIICj]; - FP divNj = 1.0 / Nj; // precompute 1.0/Nj + FP divNj = 0.0; + if (Nj > 1e-12) { + divNj = 1.0 / Nj; // precompute 1.0/Nj + } FP ext_inf_force_dummy = cont_freq_eff * divNj * params.template get>()[(AgeGroup)i] * diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 9f5914859c..88f6351a99 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -607,12 +607,11 @@ mio::IOResult>> get_graph(const return params_graph; } -mio::IOResult run(const std::string data_dir, std::string res_dir, const int num_runs = 2) +mio::IOResult run(const std::string data_dir, std::string res_dir, const int num_runs, const int num_days) { // mio::set_log_level(mio::LogLevel::critical); - auto start_date = mio::Date(2022, 8, 1); - auto end_date = mio::Date(2022, 11, 1); - const auto num_days = 90; + auto start_date = mio::Date(2021, 8, 1); + auto end_date = mio::Date(2021, 11, 1); // const int num_runs = 12; constexpr bool masks = true; constexpr bool ffp2 = true; @@ -629,7 +628,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i get_graph(start_date, end_date, num_days, data_dir, masks, ffp2, szenario_cologne, edges)); auto params_graph = created; - res_dir += "masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + + res_dir += "_masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + std::string(szenario_cologne ? "_cologne" : "") + std::string(!edges ? "_no_edges" : ""); if (mio::mpi::is_root()) @@ -645,13 +644,14 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i return n.id; }); - mio::unused(county_ids); + // mio::ExtendedGraph>> // parameter study auto parameter_study = mio::ParameterStudy< mio::osecirvvs::Simulation>>, mio::ExtendedGraph>, - mio::ExtendedGraph>>>(params_graph, 0.0, num_days, - 0.01, num_runs); + mio::ExtendedGraph< + mio::osecirvvs::Simulation>>>>( + params_graph, 0.0, num_days, 0.01, num_runs); if (mio::mpi::is_root()) { printf("Seeds: "); for (auto s : parameter_study.get_rng().get_seeds()) { @@ -679,9 +679,18 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i return node.property.base_sim.get_model(); }); + auto flows = std::vector>{}; + flows.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(flows), + [](auto&& node) { + auto& flow_node = node.property.base_sim.get_flows(); + auto interpolated_flows = mio::interpolate_simulation_result(flow_node); + return interpolated_flows; + }); + std::cout << "Run " << run_idx << " complete." << std::endl; - return std::make_pair(interpolated_result, params); + return std::make_tuple(interpolated_result, params, flows); }); if (ensemble.size() > 0) { @@ -689,11 +698,21 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i ensemble_results.reserve(ensemble.size()); auto ensemble_params = std::vector>>{}; ensemble_params.reserve(ensemble.size()); + auto ensemble_flows = std::vector>>{}; + ensemble_flows.reserve(ensemble.size()); for (auto&& run : ensemble) { - ensemble_results.emplace_back(std::move(run.first)); - ensemble_params.emplace_back(std::move(run.second)); + ensemble_results.emplace_back(std::move(std::get<0>(run))); + ensemble_params.emplace_back(std::move(std::get<1>(run))); + ensemble_flows.emplace_back(std::move(std::get<2>(run))); } BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); + + auto result_dir_run_flows = res_dir + "flows"; + if (mio::mpi::is_root()) { + boost::filesystem::create_directories(result_dir_run_flows); + printf("Saving Flow results to \"%s\".\n", result_dir_run_flows.c_str()); + } + BOOST_OUTCOME_TRY(save_results(ensemble_flows, ensemble_params, county_ids, result_dir_run_flows, false)); } return mio::success(); @@ -708,7 +727,9 @@ int main() const std::string data_dir = mio::path_join(memilio_dir, "data"); //"/localdata1/code/memilio/data"; std::string results_dir = mio::path_join(memilio_dir, "results"); - auto result = run(data_dir, results_dir); + const auto num_days = 5; + const auto num_runs = 1; + auto result = run(data_dir, results_dir, num_runs, num_days); if (!result) { printf("%s\n", result.error().formatted_message().c_str()); mio::mpi::finalize(); From 184c42e2e5781619d43fadf604848b89f84164f0 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:48:52 +0200 Subject: [PATCH 11/26] fix some small bugs, e.g. always one step for estimation of status --- .../metapopulation_mobility_detailed.h | 63 ++++++++++++------- cpp/models/ode_secirvvs/model.h | 13 ++++ cpp/models/ode_sir/model.h | 13 ++++ 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 269b4d25ab..8b575b78b5 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -108,10 +108,12 @@ auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref -void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-10) +void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-10, + const size_t max_iterations = 45) { // before moving the commuters, we need to look for negative values in vec and correct them. const size_t num_comparts = vec.size() / num_age_groups; + size_t iteration_count = 0; // check for negative values in vec while (vec.minCoeff() < tolerance) { @@ -133,7 +135,14 @@ void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_ vec(min_index) = 0; vec(max_group_indx) += min_value; - std::cout << "Negative value in vector detected and corrected. Value: " << min_value << "\n"; + // check if the number of iterations is exceeded + if (iteration_count > max_iterations) { + std::vector vec_std(vec.data(), vec.data() + vec.size()); + mio::unused(vec_std); + log_error("Number of iterations exceeded in check_negative_values_vec."); + std::exit(1); + } + iteration_count++; } } @@ -212,10 +221,9 @@ class MobilityFunctions void update_commuters(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& sim, bool is_mobility_model) { - auto& integrator_node = sim.get_integrator(); const auto t_indx_start_mobility_sim = find_time_index(sim, t, true); Eigen::VectorXd flows = Eigen::VectorXd::Zero(sim.get_flows().get_last_value().size()); - update_status_migrated(edge.get_migrated().get_last_value(), sim, integrator_node, + update_status_migrated(edge.get_migrated().get_last_value(), sim, sim.get_result().get_value(t_indx_start_mobility_sim), t, dt, flows); // if the simulation is holding a mobility model, we need to update the mobility model as well @@ -594,10 +602,6 @@ class GraphSimulationExtended : public GraphSimulationBase break; } - for (auto& node : this->m_graph.nodes()) { - node.property.mobility_sim.set_integrator(std::make_shared>()); - } - size_t indx_schedule = 0; while (t_begin + 1 > this->m_t + 1e-10) { // the graph simulation is structured in 3 steps: @@ -629,8 +633,7 @@ class GraphSimulationExtended : public GraphSimulationBase ScheduleManager::Schedule schedules; const double epsilon = 1e-10; - template - ScalarType calculate_next_dt(size_t edge_indx, size_t indx_schedule, const Edge& e) + ScalarType calculate_next_dt(size_t edge_indx, size_t indx_schedule) { auto current_node_indx = schedules.schedule_edges[edge_indx][indx_schedule]; bool in_mobility_node = schedules.mobility_schedule_edges[edge_indx][indx_schedule]; @@ -648,12 +651,12 @@ class GraphSimulationExtended : public GraphSimulationBase throw std::runtime_error("Error in schedule."); ScalarType dt_mobility; - if (indx_current == integrator_schedule_row.size() - 1) { - dt_mobility = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); - if (dt_mobility < 0.01) - dt_mobility = 0.01; + if (indx_schedule == 99 || indx_current == integrator_schedule_row.size() - 1) { + // if we are at the last iteration, we choose the minimal time step + dt_mobility = (100 - indx_schedule) * 0.01; } else { + // else, we calculate the next time step based on the next integration point dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current + 1]) - static_cast(integrator_schedule_row[indx_current])) / 100 + @@ -701,21 +704,40 @@ class GraphSimulationExtended : public GraphSimulationBase } } - void update_status_commuters(size_t indx_schedule) + void update_status_commuters(size_t indx_schedule, const size_t max_num_contacts = 20) { for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { auto& e = this->m_graph.edges()[edge_indx]; - auto next_dt = calculate_next_dt(edge_indx, indx_schedule, e); + auto next_dt = calculate_next_dt(edge_indx, indx_schedule); auto& node_to = schedules.mobility_schedule_edges[edge_indx][indx_schedule] ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.base_sim; - // TODO: Muss ich hier node_from oder node_to verwenden? Evtl ist die aktuelle config dann falsch. - // Aber sieht gut aus denke ich. Das ja getrennt von exchanges. + // get current contact pattern + auto contact_pattern_curr = get_contact_pattern(node_to.get_model()); + auto contacts_copy = contact_pattern_curr; + + auto& contact_matrix = contact_pattern_curr.get_cont_freq_mat(); + Eigen::MatrixXd scaled_matrix = contact_matrix[0].get_baseline().eval() / e.property.travel_time; + // check if there a values greater max_num_contacts in the contact matrix. if higher, set to max_num_contacts + for (auto i = 0; i < scaled_matrix.rows(); ++i) { + for (auto j = 0; j < scaled_matrix.cols(); ++j) { + if (scaled_matrix(i, j) > max_num_contacts) { + scaled_matrix(i, j) = max_num_contacts; + } + } + } + + contact_matrix[0].get_baseline() = scaled_matrix; + + set_contact_pattern(node_to.get_model(), contact_pattern_curr); m_mobility_functions.update_commuters(this->m_t, next_dt, e.property, node_to, schedules.mobility_schedule_edges[edge_indx][indx_schedule]); + + // reset contact pattern after estimating the state of the commuters + set_contact_pattern(node_to.get_model(), contacts_copy); } } @@ -793,13 +815,12 @@ make_migration_sim(FP t0, FP dt, Graph, ExtendedMigrat template ::value>> void update_status_migrated(Eigen::Ref::Vector> migrated, Sim& sim, - mio::IntegratorCore& integrator, Eigen::Ref::Vector> total, FP t, FP dt, Eigen::VectorXd& flows) { auto y0 = migrated.eval(); - auto y1 = migrated; - integrator.step( + auto y1 = migrated.setZero(); + mio::EulerIntegratorCore().step( [&](auto&& y, auto&& t_, auto&& dydt) { sim.get_model().get_derivatives(total, y, t_, dydt); }, diff --git a/cpp/models/ode_secirvvs/model.h b/cpp/models/ode_secirvvs/model.h index d9c36a9ab3..24c845b65a 100644 --- a/cpp/models/ode_secirvvs/model.h +++ b/cpp/models/ode_secirvvs/model.h @@ -913,6 +913,19 @@ auto test_commuters(Simulation& sim, Eigen::Ref> migrated, } } +template +auto get_contact_pattern(Model& model) +{ + auto& contact_pattern = model.parameters.template get>(); + return contact_pattern; +} + +template +void set_contact_pattern(Model& model, CP contact_pattern) +{ + model.parameters.template get>() = contact_pattern; +} + } // namespace osecirvvs } // namespace mio diff --git a/cpp/models/ode_sir/model.h b/cpp/models/ode_sir/model.h index 3680d8b8c5..79f73b92c8 100644 --- a/cpp/models/ode_sir/model.h +++ b/cpp/models/ode_sir/model.h @@ -122,6 +122,19 @@ class Model } }; +template +auto get_contact_pattern(Model& model) +{ + auto& contact_pattern = model.parameters.template get>(); + return contact_pattern; +} + +template +void set_contact_pattern(Model& model, CP contact_pattern) +{ + model.parameters.template get>() = contact_pattern; +} + } // namespace osir } // namespace mio From b69030eb31f3b981e2bb64ee40652fc438cbaf53 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:37:26 +0200 Subject: [PATCH 12/26] last bugs --- .../metapopulation_mobility_detailed.h | 29 ++-- .../2022_omicron_late_phase_mobility.cpp | 152 ++++++++++++++---- 2 files changed, 140 insertions(+), 41 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 8b575b78b5..dd9cab5ffe 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -714,30 +714,31 @@ class GraphSimulationExtended : public GraphSimulationBase ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.base_sim; - // get current contact pattern + // get current contact and scale it but only if mobility model auto contact_pattern_curr = get_contact_pattern(node_to.get_model()); auto contacts_copy = contact_pattern_curr; - - auto& contact_matrix = contact_pattern_curr.get_cont_freq_mat(); - Eigen::MatrixXd scaled_matrix = contact_matrix[0].get_baseline().eval() / e.property.travel_time; - // check if there a values greater max_num_contacts in the contact matrix. if higher, set to max_num_contacts - for (auto i = 0; i < scaled_matrix.rows(); ++i) { - for (auto j = 0; j < scaled_matrix.cols(); ++j) { - if (scaled_matrix(i, j) > max_num_contacts) { - scaled_matrix(i, j) = max_num_contacts; + if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) { + auto& contact_matrix = contact_pattern_curr.get_cont_freq_mat(); + Eigen::MatrixXd scaled_matrix = contact_matrix[0].get_baseline().eval() / e.property.travel_time; + // check if there a values greater max_num_contacts in the contact matrix. if higher, set to max_num_contacts + for (auto i = 0; i < scaled_matrix.rows(); ++i) { + for (auto j = 0; j < scaled_matrix.cols(); ++j) { + if (scaled_matrix(i, j) > max_num_contacts) { + scaled_matrix(i, j) = max_num_contacts; + } } } - } - contact_matrix[0].get_baseline() = scaled_matrix; - - set_contact_pattern(node_to.get_model(), contact_pattern_curr); + contact_matrix[0].get_baseline() = scaled_matrix; + set_contact_pattern(node_to.get_model(), contact_pattern_curr); + } m_mobility_functions.update_commuters(this->m_t, next_dt, e.property, node_to, schedules.mobility_schedule_edges[edge_indx][indx_schedule]); // reset contact pattern after estimating the state of the commuters - set_contact_pattern(node_to.get_model(), contacts_copy); + if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) + set_contact_pattern(node_to.get_model(), contacts_copy); } } diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 88f6351a99..10a23ead4a 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -26,6 +26,7 @@ #include "memilio/utils/date.h" #include "memilio/utils/miompi.h" #include "memilio/utils/random_number_generator.h" +#include "ode_secirvvs/parameters.h" #include "ode_secirvvs/parameters_io.h" #include "ode_secirvvs/parameter_space.h" #include "memilio/mobility/metapopulation_mobility_instant.h" @@ -299,7 +300,7 @@ static const std::map contact_locations = {{Contac * @returns any io errors that happen during reading of the files. */ mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirvvs::Parameters& params, - ScalarType scale_contacts = 1.0, ScalarType share_staying = 1.0) + ScalarType avg_tt = 0.0, ScalarType share_staying = 1.0) { auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); auto contact_matrix_transport = contact_transport_status.value(); @@ -309,32 +310,40 @@ mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirvv mio::read_mobility_plain( (data_dir / "contacts" / ("baseline_" + contact_location.second + ".txt")).string())); + // the other contact matrix containts also the transport contact matrix. Therefore, we need to subtract it. if (contact_location.second == "other") { - contact_matrices[size_t(contact_location.first)].get_baseline() = - (1 - share_staying) * abs(baseline - contact_matrix_transport) / scale_contacts + - share_staying * abs(baseline - contact_matrix_transport); - contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + baseline = abs(baseline - contact_matrix_transport); } - else { - contact_matrices[size_t(contact_location.first)].get_baseline() = - (1 - share_staying) * baseline / scale_contacts + share_staying * baseline; - contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + // Scale the number of contacts + auto scaled_baseline = (1 - share_staying) * baseline / (1 - avg_tt) + share_staying * baseline; + // Because we only model the acitvity from commuters, some age group should just have the unscaled contacts + Eigen::MatrixXd baseline_adjusted = Eigen::MatrixXd::Zero(scaled_baseline.rows(), scaled_baseline.cols()); + for (auto i = 0; i < 6; i++) { + for (auto j = 0; j < 6; j++) { + if ((i >= 2 && i <= 5) || (j >= 2 && j <= 5)) { + baseline_adjusted(i, j) = scaled_baseline(i, j); + } + else { + baseline_adjusted(i, j) = baseline(i, j); + } + } } + contact_matrices[size_t(contact_location.first)].get_baseline() = baseline_adjusted; + contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); } params.get>() = mio::UncertainContactMatrix(contact_matrices); return mio::success(); } -mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirvvs::Parameters& params, - ScalarType scale_contacts = 1.0) +mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirvvs::Parameters& params) { auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); auto contact_matrix_transport = contact_transport_status.value(); auto contact_matrices = mio::ContactMatrixGroup(1, size_t(params.get_num_groups())); // ScalarType const polymod_share_contacts_transport = 1 / 0.2770885028949545; - contact_matrices[0].get_baseline() = contact_matrix_transport / scale_contacts; + contact_matrices[0].get_baseline() = contact_matrix_transport; contact_matrices[0].get_minimum() = Eigen::MatrixXd::Zero(6, 6); params.get>() = mio::UncertainContactMatrix(contact_matrices); @@ -342,6 +351,47 @@ mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio return mio::success(); } +mio::IOResult scale_contacts_local(mio::ExtendedGraph>& params_graph, + const fs::path& data_dir, const std::string mobility_data_dir, + const std::vector commuting_weights) +{ + BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); + // average travel time + const ScalarType avg_traveltime = std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, + [](double sum, const auto& e) { + return sum + e.property.travel_time; + }) / + params_graph.edges().size(); + + // average share of commuters for all counties relative to the total population + const ScalarType total_population = std::accumulate( + params_graph.nodes().begin(), params_graph.nodes().end(), 0.0, [commuting_weights](double sum, const auto& n) { + return sum + n.property.base_sim.populations.get_group_total(mio::AgeGroup(0)) * commuting_weights[0] + + n.property.base_sim.populations.get_group_total(mio::AgeGroup(1)) * commuting_weights[1] + + n.property.base_sim.populations.get_group_total(mio::AgeGroup(2)) * commuting_weights[2] + + n.property.base_sim.populations.get_group_total(mio::AgeGroup(3)) * commuting_weights[3] + + n.property.base_sim.populations.get_group_total(mio::AgeGroup(4)) * commuting_weights[4] + + n.property.base_sim.populations.get_group_total(mio::AgeGroup(5)) * commuting_weights[5]; + }); + const ScalarType num_commuters = std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, + [mobility_data_commuter](double sum, const auto& e) { + auto start_node = e.start_node_idx; + auto end_node = e.end_node_idx; + return sum + mobility_data_commuter(start_node, end_node); + }); + const ScalarType avg_commuter_share = num_commuters / total_population; + + std::cout << "avg_commuter_share: " << avg_commuter_share << std::endl; + std::cout << "avg_traveltime: " << avg_traveltime << std::endl; + + // scale all contact matrices + for (auto& node : params_graph.nodes()) { + BOOST_OUTCOME_TRY( + set_contact_matrices(data_dir, node.property.base_sim.parameters, avg_traveltime, avg_commuter_share)); + } + return mio::success(); +} + // reset population in graph void init_pop_cologne_szenario(mio::Graph, mio::MigrationParameters>& graph, const int id_cologne) @@ -406,13 +456,13 @@ void init_pop_cologne_szenario(mio::Graph, mio::Mi * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. */ template -mio::IOResult set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mio::Date end_date, - const fs::path& data_dir, const std::string& population_data_path, - const std::string& stay_times_data_path, bool is_node_for_county, - mio::ExtendedGraph>& params_graph, ReadFunction&& read_func, - NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, - FP scaling_factor_icu, FP tnt_capacity_factor, int num_days = 0, - bool export_time_series = false, bool rki_age_groups = true) +mio::IOResult +set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mio::Date end_date, + const fs::path& data_dir, const std::string& population_data_path, const std::string& stay_times_data_path, + bool is_node_for_county, mio::ExtendedGraph>& params_graph, + ReadFunction&& read_func, NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, + FP scaling_factor_icu, FP tnt_capacity_factor, int num_days = 0, bool export_time_series = false, + bool rki_age_groups = true, bool masks = false, bool ffp2 = false) { BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); @@ -464,7 +514,46 @@ mio::IOResult set_nodes(const mio::osecirvvs::Parameters& params, mio: auto mobility_model = nodes[node_idx]; mobility_model.populations.set_total(0); - params_graph.add_node(1, nodes[node_idx], mobility_model, duration_stay((Eigen::Index)node_idx)); + // reduce transmission on contact due to mask obligation in mobility node + // first age group not able to (properly) wear masks + if (masks) { + + const double fact_surgical_mask = 0.1; + const double fact_ffp2 = 0.001; + + double factor_mask[] = {1, fact_surgical_mask, fact_ffp2, fact_ffp2, fact_ffp2, fact_ffp2}; + if (!ffp2) { + for (size_t j = 1; j < 6; j++) { + factor_mask[j] = factor_mask[j] * fact_surgical_mask / fact_ffp2; + } + } + + double fac_variant = 1.45; //1.94; //https://doi.org/10.7554/eLife.78933 + double transmissionProbabilityOnContactMin[] = {0.02 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, + 0.05 * fac_variant, 0.08 * fac_variant, 0.1 * fac_variant}; + + double transmissionProbabilityOnContactMax[] = {0.04 * fac_variant, 0.07 * fac_variant, 0.07 * fac_variant, + 0.07 * fac_variant, 0.10 * fac_variant, 0.15 * fac_variant}; + for (int i = 0; i < 6; i++) { + transmissionProbabilityOnContactMin[i] = transmissionProbabilityOnContactMin[i] * factor_mask[i]; + transmissionProbabilityOnContactMax[i] = transmissionProbabilityOnContactMax[i] * factor_mask[i]; + } + array_assign_uniform_distribution( + mobility_model.parameters.template get>(), + transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); + } + + for (int t_idx = 0; t_idx < num_days; ++t_idx) { + auto t = mio::SimulationDay((size_t)t_idx); + for (auto j = mio::AgeGroup(0); j < params.get_num_groups(); j++) { + mobility_model.parameters.template get>()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; + // mobility_model.parameters.template get()[{j, t}] = 0; + } + } + + params_graph.add_node(node_ids[node_idx], nodes[node_idx], mobility_model, + duration_stay((Eigen::Index)node_idx)); } return mio::success(); } @@ -528,7 +617,6 @@ set_edges(const std::string& travel_times_dir, const std::string mobility_data_d params_graph.add_edge(county_idx_i, county_idx_j, std::move(mobility_coeffs), travel_times(county_idx_i, county_idx_j), path_mobility[county_idx_i][county_idx_j]); - // g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); } } } @@ -549,7 +637,7 @@ mio::IOResult>> get_graph(const const std::string& data_dir, bool masks, bool ffp2, bool szenario_cologne, bool edges) { - mio::unused(num_days, masks, ffp2, szenario_cologne, edges); + mio::unused(szenario_cologne); std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); @@ -575,12 +663,17 @@ mio::IOResult>> get_graph(const params, start_date, end_date, data_dir, mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"), durations_dir, true, params_graph, read_function_nodes, node_id_function, scaling_factor_infected, scaling_factor_icu, - tnt_capacity_factor, num_days, false); + tnt_capacity_factor, num_days, false, true, ffp2, masks); if (!set_nodes_status) { return set_nodes_status.error(); } + // iterate over all nodes and set the contact_matrix for the mobility models + for (auto& node : params_graph.nodes()) { + BOOST_OUTCOME_TRY(set_contact_matrices_transport(data_dir, node.property.base_sim.parameters)); + } + // set edges if (edges) { auto migrating_compartments = {mio::osecirvvs::InfectionState::SusceptibleNaive, @@ -603,6 +696,11 @@ mio::IOResult>> get_graph(const if (!set_edges_status) { return set_edges_status.error(); } + + // if we have edges/mobility, we also need to scale the local contacts + BOOST_OUTCOME_TRY(scale_contacts_local( + params_graph, data_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), + std::vector{0., 0., 1.0, 1.0, 0.33, 0., 0.})); } return params_graph; @@ -628,7 +726,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i get_graph(start_date, end_date, num_days, data_dir, masks, ffp2, szenario_cologne, edges)); auto params_graph = created; - res_dir += "_masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + + res_dir += "/masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + std::string(szenario_cologne ? "_cologne" : "") + std::string(!edges ? "_no_edges" : ""); if (mio::mpi::is_root()) @@ -707,7 +805,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i } BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); - auto result_dir_run_flows = res_dir + "flows"; + auto result_dir_run_flows = res_dir + "/flows"; if (mio::mpi::is_root()) { boost::filesystem::create_directories(result_dir_run_flows); printf("Saving Flow results to \"%s\".\n", result_dir_run_flows.c_str()); @@ -727,8 +825,8 @@ int main() const std::string data_dir = mio::path_join(memilio_dir, "data"); //"/localdata1/code/memilio/data"; std::string results_dir = mio::path_join(memilio_dir, "results"); - const auto num_days = 5; - const auto num_runs = 1; + const auto num_days = 90; + const auto num_runs = 10; auto result = run(data_dir, results_dir, num_runs, num_days); if (!result) { printf("%s\n", result.error().formatted_message().c_str()); From a4706d54b3a21b93e9a79f66596e7f25eeb84b23 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 20 Aug 2024 08:29:41 +0200 Subject: [PATCH 13/26] some adjustments for mpi --- .../mobility/metapopulation_mobility_detailed.h | 8 ++++---- cpp/simulations/2022_omicron_late_phase_mobility.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index dd9cab5ffe..9790436a24 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -108,8 +108,8 @@ auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref -void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-10, - const size_t max_iterations = 45) +void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-7, + const size_t max_iterations = 100) { // before moving the commuters, we need to look for negative values in vec and correct them. const size_t num_comparts = vec.size() / num_age_groups; @@ -764,8 +764,8 @@ class GraphSimulationExtended : public GraphSimulationBase std::vector(n.property.base_sim.get_result().get_last_value().data(), n.property.base_sim.get_result().get_last_value().data() + n.property.base_sim.get_result().get_last_value().size()); - std::cout << "Negative Value in local node detected." - << "\n"; + log_error("Negative Value " + std::to_string(n_indx) + " at time " + std::to_string(indx_schedule) + + " in local node detected."); } } } diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 10a23ead4a..93f6189e89 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -381,8 +381,10 @@ mio::IOResult scale_contacts_local(mio::ExtendedGraph& params, mio::Date start_date, mi mobility_model.populations.set_total(0); // reduce transmission on contact due to mask obligation in mobility node - // first age group not able to (properly) wear masks + // first age group not able to (properly) wear masks if (masks) { const double fact_surgical_mask = 0.1; @@ -523,7 +525,7 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi double factor_mask[] = {1, fact_surgical_mask, fact_ffp2, fact_ffp2, fact_ffp2, fact_ffp2}; if (!ffp2) { - for (size_t j = 1; j < 6; j++) { + for (size_t j = 0; j < 6; j++) { factor_mask[j] = factor_mask[j] * fact_surgical_mask / fact_ffp2; } } From 4e66ebabbd030192c5a83b12527b0bacf4f4478f Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:51:03 +0200 Subject: [PATCH 14/26] migration -> mobility --- cpp/memilio/compartments/parameter_studies.h | 4 +-- .../metapopulation_mobility_detailed.h | 36 +++++++++---------- .../metapopulation_mobility_instant.h | 2 +- .../2022_omicron_late_phase_mobility.cpp | 4 +-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cpp/memilio/compartments/parameter_studies.h b/cpp/memilio/compartments/parameter_studies.h index 0c3d0c859b..660bd1b265 100644 --- a/cpp/memilio/compartments/parameter_studies.h +++ b/cpp/memilio/compartments/parameter_studies.h @@ -74,8 +74,8 @@ namespace mio * @tparam ParametersGraph stores the parameters of the simulation. This is the input of ParameterStudies. * @tparam SimulationGraph stores simulations and their results of each run. This is the output of ParameterStudies for each run. */ -template >, - class SimulationGraph = Graph, MigrationEdge>> +template >, + class SimulationGraph = Graph, MobilityEdge>> class ParameterStudy { public: diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 9790436a24..e9f12d8a87 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -63,21 +63,21 @@ struct ExtendedNodeProperty { }; template -class ExtendedMigrationEdge : public MigrationEdge +class ExtendedMobilityEdge : public MobilityEdge { public: double travel_time; std::vector path; - ExtendedMigrationEdge(const MigrationParameters& params, double tt, std::vector p) - : MigrationEdge(params) + ExtendedMobilityEdge(const MobilityParameters& params, double tt, std::vector p) + : MobilityEdge(params) , travel_time(tt) , path(p) { } - ExtendedMigrationEdge(const Eigen::VectorXd& coeffs, double tt, std::vector p) - : MigrationEdge(coeffs) + ExtendedMobilityEdge(const Eigen::VectorXd& coeffs, double tt, std::vector p) + : MobilityEdge(coeffs) , travel_time(tt) , path(p) { @@ -85,7 +85,7 @@ class ExtendedMigrationEdge : public MigrationEdge auto& get_migrated() { - return this->m_migrated; + return this->m_mobile_population; } auto& get_return_times() { @@ -98,11 +98,11 @@ class ExtendedMigrationEdge : public MigrationEdge }; template -using ExtendedGraph = Graph, ExtendedMigrationEdge>; +using ExtendedGraph = Graph, ExtendedMobilityEdge>; -// Default implementation when get_migration_factors is not defined for Sim -template ::value, void*> = nullptr> -auto get_migration_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref& y) +// Default implementation when get_mobility_factors is not defined for Sim +template ::value, void*> = nullptr> +auto get_mobility_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref& y) { return Eigen::VectorXd::Ones(y.rows()); } @@ -183,7 +183,7 @@ template class MobilityFunctions { public: - void init_mobility(FP t, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void init_mobility(FP t, ExtendedMobilityEdge& edge, Sim& from_sim, Sim& to_sim) { const auto t_indx_start_mobility_sim_from = find_time_index(from_sim, t, false); @@ -192,7 +192,7 @@ class MobilityFunctions edge.get_migrated().add_time_point( t, (results_from.get_value(t_indx_start_mobility_sim_from).array() * edge.get_parameters().get_coefficients().get_matrix_at(t).array() * - get_migration_factors(from_sim, t, results_from.get_value(t_indx_start_mobility_sim_from)).array()) + get_mobility_factors(from_sim, t, results_from.get_value(t_indx_start_mobility_sim_from)).array()) .matrix()); edge.get_return_times().add_time_point(t); @@ -204,7 +204,7 @@ class MobilityFunctions from_sim.get_result().get_last_value() -= edge.get_migrated().get_last_value(); } - void move_migrated(FP t, ExtendedMigrationEdge& edge, Sim& from_sim, Sim& to_sim) + void move_migrated(FP t, ExtendedMobilityEdge& edge, Sim& from_sim, Sim& to_sim) { // When moving from one regional entity/model to another, we need to update the local population. // check_negative_values_vec needs to be called once since its checks for negative values and corrects them. @@ -219,7 +219,7 @@ class MobilityFunctions check_negative_values_vec(to_sim.get_result().get_value(t_indx_sim_to_arrival), num_age_groups); } - void update_commuters(FP t, FP dt, ExtendedMigrationEdge& edge, Sim& sim, bool is_mobility_model) + void update_commuters(FP t, FP dt, ExtendedMobilityEdge& edge, Sim& sim, bool is_mobility_model) { const auto t_indx_start_mobility_sim = find_time_index(sim, t, true); Eigen::VectorXd flows = Eigen::VectorXd::Zero(sim.get_flows().get_last_value().size()); @@ -241,7 +241,7 @@ class MobilityFunctions } } - void delete_migrated(ExtendedMigrationEdge& edge) + void delete_migrated(ExtendedMobilityEdge& edge) { for (Eigen::Index i = edge.get_return_times().get_num_time_points() - 1; i >= 0; --i) { edge.get_migrated().remove_time_point(i); @@ -793,11 +793,11 @@ class GraphSimulationExtended : public GraphSimulationBase }; template -GraphSimulationExtended, ExtendedMigrationEdge>, MobilityFunctions> -make_migration_sim(FP t0, FP dt, Graph, ExtendedMigrationEdge>&& graph) +GraphSimulationExtended, ExtendedMobilityEdge>, MobilityFunctions> +make_mobility_sim(FP t0, FP dt, Graph, ExtendedMobilityEdge>&& graph) { auto migration_modes = MobilityFunctions(); - return GraphSimulationExtended, ExtendedMigrationEdge>, + return GraphSimulationExtended, ExtendedMobilityEdge>, MobilityFunctions>(t0, dt, std::move(graph), {}, migration_modes); } diff --git a/cpp/memilio/mobility/metapopulation_mobility_instant.h b/cpp/memilio/mobility/metapopulation_mobility_instant.h index 72e46bbda6..16c6501bd7 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_instant.h +++ b/cpp/memilio/mobility/metapopulation_mobility_instant.h @@ -295,7 +295,7 @@ class MobilityEdge template void apply_mobility(FP t, FP dt, SimulationNode& node_from, SimulationNode& node_to); -private: +protected: MobilityParameters m_parameters; TimeSeries m_mobile_population; TimeSeries m_return_times; diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 93f6189e89..c883a434f5 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -395,7 +395,7 @@ mio::IOResult scale_contacts_local(mio::ExtendedGraph, mio::MigrationParameters>& graph, +void init_pop_cologne_szenario(mio::Graph, mio::MobilityParameters>& graph, const int id_cologne) { std::vector> immunity = {{0.04, 0.61, 0.35}, {0.04, 0.61, 0.35}, {0.075, 0.62, 0.305}, @@ -587,7 +587,7 @@ set_edges(const std::string& travel_times_dir, const std::string mobility_data_d // mobility coefficients have the same number of components as the contact matrices. // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) - auto mobility_coeffs = mio::MigrationCoefficientGroup(contact_locations_size, populations.numel()); + auto mobility_coeffs = mio::MobilityCoefficientGroup(contact_locations_size, populations.numel()); auto num_age_groups = (size_t)params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups(); commuting_weights = From 3e2985d8661ad935217dfa4b369cedd32db7f4e4 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:56:52 +0200 Subject: [PATCH 15/26] typo in sampling --- cpp/models/ode_secirvvs/parameter_space.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/models/ode_secirvvs/parameter_space.h b/cpp/models/ode_secirvvs/parameter_space.h index 47447365aa..3b161b5f4a 100644 --- a/cpp/models/ode_secirvvs/parameter_space.h +++ b/cpp/models/ode_secirvvs/parameter_space.h @@ -238,6 +238,7 @@ ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_ma //sample global parameters auto& shared_params_base_model = graph.nodes()[0].property.base_sim; + auto& shared_mobility_model = graph.nodes()[0].property.mobility_sim; draw_sample_infection(shared_params_base_model); auto& shared_contacts = shared_params_base_model.parameters.template get>(); shared_contacts.draw_sample_dampings(); @@ -271,7 +272,7 @@ ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_ma // without vaccinations and the contact patterns are different. auto& node_mobility_model = params_node.property.mobility_sim; auto node_mobility_contacts = node_mobility_model.parameters.template get>(); - node_mobility_model.parameters = shared_params_base_model.parameters; + node_mobility_model.parameters = shared_mobility_model.parameters; node_mobility_model.parameters.template get>() = node_mobility_contacts; // set vaccination parameters to zero From 164eb9a7170ad5c37ed8236a25d9b00848357735 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:40:52 +0200 Subject: [PATCH 16/26] interpolate time series during sim --- .../metapopulation_mobility_detailed.h | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index e9f12d8a87..fc4a7d0066 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -625,6 +625,27 @@ class GraphSimulationExtended : public GraphSimulationBase for (auto& n : this->m_graph.nodes()) { n.property.mobility_sim.get_result().get_last_value().setZero(); } + + // to save memory, we interpolate each time series after every day if t > 20 + if (this->m_t > 20) { + for (auto& n : this->m_graph.nodes()) { + // base sim + auto& result_node_local = n.property.base_sim.get_result(); + auto interpolated_result = interpolate_simulation_result(result_node_local); + auto& flow_node_local = n.property.base_sim.get_flows(); + auto interpolated_flows = interpolate_simulation_result(flow_node_local); + n.property.base_sim.get_result() = interpolated_result; + n.property.base_sim.get_flows() = interpolated_flows; + + // mobility sim + auto& result_node_mobility = n.property.mobility_sim.get_result(); + interpolated_result = interpolate_simulation_result(result_node_mobility); + auto& flow_node_mobility = n.property.mobility_sim.get_flows(); + interpolated_flows = interpolate_simulation_result(flow_node_mobility); + n.property.mobility_sim.get_result() = interpolated_result; + n.property.mobility_sim.get_flows() = interpolated_flows; + } + } } } From d376d803e9b81b75b39e971024b29466b6c7a8f4 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:12:57 +0100 Subject: [PATCH 17/26] fix bugs from merge --- cpp/memilio/compartments/flow_model.h | 18 +++- cpp/memilio/compartments/parameter_studies.h | 100 ++++++++++++------ .../metapopulation_mobility_detailed.h | 25 +++-- cpp/models/ode_secirvvs/parameter_space.h | 12 +-- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/cpp/memilio/compartments/flow_model.h b/cpp/memilio/compartments/flow_model.h index b905e60b81..4cfd820263 100644 --- a/cpp/memilio/compartments/flow_model.h +++ b/cpp/memilio/compartments/flow_model.h @@ -55,8 +55,8 @@ using filtered_tuple_t = decltype(filter_tuple(std::declval() // Remove all occurrences of OmittedTag from the types in an Index = IndexTemplate. template class IndexTemplate, class Index> -using filtered_index_t = decltype(as_index( - std::declval()))>>())); +using filtered_index_t = decltype( + as_index(std::declval()))>>())); } //namespace details @@ -203,12 +203,22 @@ class FlowModel : public CompartmentalModel return index_of_type_v, Flows>; } - Eigen::VectorXd get_flow_values() const + /** + * @brief Returns the current flow values. + * + * @return A constant reference to an Eigen::VectorX containing the current flow values. + */ + Eigen::VectorX& get_flow_values() const { return m_flow_values; } - void set_flow_values(const Eigen::VectorXd& flows) + /** + * @brief Sets the flow values. + * + * @param flows A constant reference to an Eigen::VectorX containing flow values. + */ + void set_flow_values(const Eigen::VectorX flows) { m_flow_values = flows; } diff --git a/cpp/memilio/compartments/parameter_studies.h b/cpp/memilio/compartments/parameter_studies.h index 277470ec9b..746c95915f 100644 --- a/cpp/memilio/compartments/parameter_studies.h +++ b/cpp/memilio/compartments/parameter_studies.h @@ -355,49 +355,87 @@ class ParameterStudy } private: - template - auto create_sampled_simulation(SampleGraphFunction sample_graph) + /** + * @brief Adds a node to the graph using the overload based on the node's property. + * + * This function checks whether the node property type has a 'stay_duration' member. + * If so, it uses the specialized version that extracts additional simulation parameters. + * Otherwise, we just use the simulation time (m_t0) and integration step size (m_dt_integration). + * + * @tparam GraphType The type of the graph. + * @tparam NodeType The type of the node. + * @param graph The graph to which the node will be added. + * @param node The node to add. + */ + template + void add_node(GraphType& graph, const NodeType& node) { - SimulationGraph sim_graph; + // Deduce the property type of the node. + using PropertyType = std::decay_t; - auto sampled_graph = sample_graph(m_graph); - for (auto&& node : sampled_graph.nodes()) { - using PropertyType = typename std::decay::type; - add_node_with_properties(sim_graph, node, - std::integral_constant::value>{}); + // If the property type has a member called 'stay_duration', use the specialized version. + if constexpr (has_stay_duration::value) { + graph.add_node(node.id, node.property.base_sim, node.property.mobility_sim, node.property.stay_duration); } - for (auto&& edge : sampled_graph.edges()) { - using PropertyType = typename std::decay::type; - add_edge_with_properties(sim_graph, edge, std::integral_constant < bool, - has_travel_time::value&& has_path::value > {}); + else { + graph.add_node(node.id, node.property, m_t0, m_dt_integration); } - - return make_mobility_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); } - template - void add_node_with_properties(GraphType& graph, const NodeType& node, std::false_type) + /** + * @brief Adds an edge to the graph using the appropriate overload based on the edge's property. + * + * This function checks whether the edge property type has both 'travel_time' and 'path' members. + * If so, it uses the specialized version that uses additional parameters. + * Otherwise, we just use the edge property. + * + * @tparam GraphType The type of the graph. + * @tparam EdgeType The type of the edge. + * @param graph The graph to which the edge will be added. + * @param edge The edge to add. + */ + template + void add_edge(GraphType& graph, const EdgeType& edge) { - graph.add_node(node.id, node.property, m_t0, m_dt_integration); - } + // Deduce the property type of the edge. + using PropertyType = std::decay_t; - template - void add_node_with_properties(GraphType& graph, const NodeType& node, std::true_type) - { - graph.add_node(node.id, node.property.base_sim, node.property.mobility_sim, node.property.stay_duration); + // Check if the property type has both 'travel_time' and 'path' members. + if constexpr (has_travel_time::value && has_path::value) { + graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property.get_parameters(), + edge.property.travel_time, edge.property.path); + } + else { + graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property); + } } - template - void add_edge_with_properties(GraphType& graph, const EdgeType& edge, std::false_type) + /** + * @brief Creates a mobility simulation based on a sampled graph. + * @tparam SampleGraphFunction The type of the function that samples the graph. + * @param sample_graph A function that takes the internal graph and returns a sampled graph. + * @return A mobility simulation created from the sampled simulation graph. + */ + template + auto create_sampled_simulation(SampleGraphFunction sample_graph) { - graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property); - } + // Create an empty simulation graph. + SimulationGraph sim_graph; - template - void add_edge_with_properties(GraphType& graph, const EdgeType& edge, std::true_type) - { - graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property.get_parameters(), - edge.property.travel_time, edge.property.path); + // Sample the original graph using the provided sampling function. + auto sampled_graph = sample_graph(m_graph); + + // Iterate over each node in the sampled graph and add it to the simulation graph. + for (auto&& node : sampled_graph.nodes()) { + add_node(sim_graph, node); + } + + // Iterate over each edge in the sampled graph and add it to the simulation graph. + for (auto&& edge : sampled_graph.edges()) { + add_edge(sim_graph, edge); + } + + return make_mobility_sim(m_t0, m_dt_graph_sim, std::move(sim_graph)); } std::vector distribute_runs(size_t num_runs, int num_procs) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index fc4a7d0066..63d3365501 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -108,7 +108,7 @@ auto get_mobility_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref -void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-7, +void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-7, const size_t max_iterations = 100) { // before moving the commuters, we need to look for negative values in vec and correct them. @@ -540,7 +540,11 @@ class ScheduleManager }; template -class GraphSimulationExtended : public GraphSimulationBase +class GraphSimulationExtended + : public GraphSimulationBase, + std::function> { public: using node_function = std::function; @@ -548,9 +552,12 @@ class GraphSimulationExtended : public GraphSimulationBase std::function; - GraphSimulationExtended(double t0, double dt, const Graph& g, const node_function& node_func, - MobilityFunctions modes) - : GraphSimulationBase(t0, dt, g, node_func, {}) + GraphSimulationExtended(double t0, double dt, Graph& g, const node_function& node_func, MobilityFunctions modes) + : GraphSimulationBase, + std::function>(t0, dt, std::move(g), + node_func, {}) , m_mobility_functions(modes) { ScheduleManager schedule_manager(100); // Assuming 100 timesteps @@ -558,7 +565,11 @@ class GraphSimulationExtended : public GraphSimulationBase } GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MobilityFunctions modes) - : GraphSimulationBase(t0, dt, std::move(g), node_func, {}) + : GraphSimulationBase, + std::function>( + t0, dt, std::forward(g), node_func, {}) , m_mobility_functions(modes) { ScheduleManager schedule_manager(100); // Assuming 100 timesteps @@ -852,4 +863,4 @@ void update_status_migrated(Eigen::Ref::Vector> migrated } } // namespace mio -#endif //METAPOPULATION_MOBILITY_DETAILED_H \ No newline at end of file +#endif //METAPOPULATION_MOBILITY_DETAILED_H diff --git a/cpp/models/ode_secirvvs/parameter_space.h b/cpp/models/ode_secirvvs/parameter_space.h index 4f20ce9aca..e43dc7aa3b 100644 --- a/cpp/models/ode_secirvvs/parameter_space.h +++ b/cpp/models/ode_secirvvs/parameter_space.h @@ -258,14 +258,14 @@ ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_ma auto local_icu_capacity = node_model_local.parameters.template get>(); auto local_tnt_capacity = node_model_local.parameters.template get>(); auto local_holidays = node_model_local.parameters.template get>().get_school_holidays(); - auto local_daily_v1 = node_model_local.parameters.template get>(); - auto local_daily_v2 = node_model_local.parameters.template get>(); + auto local_daily_v1 = node_model_local.parameters.template get>(); + auto local_daily_v2 = node_model_local.parameters.template get>(); node_model_local.parameters = shared_params_base_model.parameters; node_model_local.parameters.template get>() = local_icu_capacity; node_model_local.parameters.template get>() = local_tnt_capacity; node_model_local.parameters.template get>().get_school_holidays() = local_holidays; - node_model_local.parameters.template get>() = local_daily_v1; - node_model_local.parameters.template get>() = local_daily_v2; + node_model_local.parameters.template get>() = local_daily_v1; + node_model_local.parameters.template get>() = local_daily_v2; node_model_local.parameters.template get>().make_matrix(); node_model_local.apply_constraints(); @@ -278,8 +278,8 @@ ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_ma node_mobility_model.parameters.template get>() = node_mobility_contacts; // set vaccination parameters to zero - node_mobility_model.parameters.template get>().array().setConstant(0); - node_mobility_model.parameters.template get>().array().setConstant(0); + node_mobility_model.parameters.template get>().array().setConstant(0); + node_mobility_model.parameters.template get>().array().setConstant(0); // adjust the transmission factor for the mobility model based on the usage of masks for (auto age = AgeGroup(0); age < node_mobility_model.parameters.get_num_groups(); ++age) { From e00f2d5fffdc768cfc7a5828d1ea02afd5aea8e9 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:37:53 +0100 Subject: [PATCH 18/26] fix example, cp as part of model class --- cpp/examples/ode_seir_detailed_mobility.cpp | 2 +- .../metapopulation_mobility_detailed.h | 6 +-- cpp/models/ode_seir/model.h | 31 +++++++++++++ cpp/models/ode_sir/model.h | 45 +++++++++++++------ 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/cpp/examples/ode_seir_detailed_mobility.cpp b/cpp/examples/ode_seir_detailed_mobility.cpp index faadefdb8f..d757fda332 100644 --- a/cpp/examples/ode_seir_detailed_mobility.cpp +++ b/cpp/examples/ode_seir_detailed_mobility.cpp @@ -73,7 +73,7 @@ int main() g.add_edge(0, 1, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path1); g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::oseir::InfectionState::Count, 0.01), traveltime, path2); - auto sim = mio::make_migration_sim(t0, dt, std::move(g)); + auto sim = mio::make_mobility_sim(t0, dt, std::move(g)); sim.advance(tmax); // results node 1 diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 63d3365501..07bf5416d5 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -747,7 +747,7 @@ class GraphSimulationExtended : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.base_sim; // get current contact and scale it but only if mobility model - auto contact_pattern_curr = get_contact_pattern(node_to.get_model()); + auto contact_pattern_curr = node_to.get_model().get_contact_pattern(); auto contacts_copy = contact_pattern_curr; if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) { auto& contact_matrix = contact_pattern_curr.get_cont_freq_mat(); @@ -762,7 +762,7 @@ class GraphSimulationExtended } contact_matrix[0].get_baseline() = scaled_matrix; - set_contact_pattern(node_to.get_model(), contact_pattern_curr); + node_to.get_model().set_contact_pattern(contact_pattern_curr); } m_mobility_functions.update_commuters(this->m_t, next_dt, e.property, node_to, @@ -770,7 +770,7 @@ class GraphSimulationExtended // reset contact pattern after estimating the state of the commuters if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) - set_contact_pattern(node_to.get_model(), contacts_copy); + node_to.get_model().set_contact_pattern(contacts_copy); } } diff --git a/cpp/models/ode_seir/model.h b/cpp/models/ode_seir/model.h index d771bae94c..e1af7124a3 100644 --- a/cpp/models/ode_seir/model.h +++ b/cpp/models/ode_seir/model.h @@ -206,6 +206,37 @@ class Model return mio::success(static_cast(result)); } + /** + * @brief Returns a const reference to the contact pattern. + * + * @return A const reference to the contact pattern. + */ + const auto& get_contact_pattern() const + { + return this->parameters.template get>(); + } + + /** + * @brief Returns a reference to the contact pattern. + * + * @return A reference to the contact pattern. + */ + auto& get_contact_pattern() + { + return this->parameters.template get>(); + } + + /** + * @brief Sets the contact pattern. + * + * @tparam T The type of the new contact pattern. + * @param contact_pattern The new contact pattern to be set. + */ + template + void set_contact_pattern(T contact_pattern) + { + this->parameters.template get>() = std::move(contact_pattern); + } /** * serialize this. * @see mio::serialize diff --git a/cpp/models/ode_sir/model.h b/cpp/models/ode_sir/model.h index 6a24a9d38d..d15aabd729 100644 --- a/cpp/models/ode_sir/model.h +++ b/cpp/models/ode_sir/model.h @@ -92,6 +92,38 @@ class Model } } + /** + * @brief Returns a const reference to the contact pattern. + * + * @return A const reference to the contact pattern. + */ + const auto& get_contact_pattern() const + { + return this->parameters.template get>(); + } + + /** + * @brief Returns a reference to the contact pattern. + * + * @return A reference to the contact pattern. + */ + auto& get_contact_pattern() + { + return this->parameters.template get>(); + } + + /** + * @brief Sets the contact pattern. + * + * @tparam T The type of the new contact pattern. + * @param contact_pattern The new contact pattern to be set. + */ + template + void set_contact_pattern(T contact_pattern) + { + this->parameters.template get>() = std::move(contact_pattern); + } + /** * serialize this. * @see mio::serialize @@ -123,19 +155,6 @@ class Model } }; -template -auto get_contact_pattern(Model& model) -{ - auto& contact_pattern = model.parameters.template get>(); - return contact_pattern; -} - -template -void set_contact_pattern(Model& model, CP contact_pattern) -{ - model.parameters.template get>() = contact_pattern; -} - } // namespace osir } // namespace mio From 633568cff3d16f0d029a069d42be49131aecf0c6 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:57:05 +0100 Subject: [PATCH 19/26] fix simulations, some type conv --- .../metapopulation_mobility_detailed.h | 4 +-- cpp/models/ode_secir/model.h | 32 +++++++++++++++++ cpp/models/ode_secirts/model.h | 34 ++++++++++++++++++- cpp/models/ode_secirvvs/model.h | 32 +++++++++++++++++ .../2022_omicron_late_phase_mobility.cpp | 6 ++-- 5 files changed, 102 insertions(+), 6 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 07bf5416d5..c965e44f51 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -736,7 +736,7 @@ class GraphSimulationExtended } } - void update_status_commuters(size_t indx_schedule, const size_t max_num_contacts = 20) + void update_status_commuters(size_t indx_schedule, const double max_num_contacts = 20.) { for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { auto& e = this->m_graph.edges()[edge_indx]; @@ -802,7 +802,7 @@ class GraphSimulationExtended } } - void handle_last_time_step(int indx_schedule) + void handle_last_time_step(size_t indx_schedule) { if (indx_schedule == 99) { auto edge_index = 0; diff --git a/cpp/models/ode_secir/model.h b/cpp/models/ode_secir/model.h index 76ea3992c5..93144bb438 100755 --- a/cpp/models/ode_secir/model.h +++ b/cpp/models/ode_secir/model.h @@ -209,6 +209,38 @@ class Model : public FlowModelparameters.template get>(); + } + + /** + * @brief Returns a reference to the contact pattern. + * + * @return A reference to the contact pattern. + */ + auto& get_contact_pattern() + { + return this->parameters.template get>(); + } + + /** + * @brief Sets the contact pattern. + * + * @tparam T The type of the new contact pattern. + * @param contact_pattern The new contact pattern to be set. + */ + template + void set_contact_pattern(T contact_pattern) + { + this->parameters.template get>() = std::move(contact_pattern); + } + /** * serialize this. * @see mio::serialize diff --git a/cpp/models/ode_secirts/model.h b/cpp/models/ode_secirts/model.h index ddc935f37d..bc5c1b5407 100644 --- a/cpp/models/ode_secirts/model.h +++ b/cpp/models/ode_secirts/model.h @@ -265,7 +265,7 @@ class Model // effective contact rate by contact rate between groups i and j and damping j FP season_val = (1 + params.template get>() * sin(3.141592653589793 * - (std::fmod((params.template get() + t), 365.0) / 182.5 + 0.5))); + (std::fmod((params.template get() + t), 365.0) / 182.5 + 0.5))); FP cont_freq_eff = season_val * contact_matrix.get_matrix_at(t)(static_cast((size_t)i), static_cast((size_t)j)); // without died people @@ -633,6 +633,38 @@ class Model return smoothed_vaccinations; } + /** + * @brief Returns a const reference to the contact pattern. + * + * @return A const reference to the contact pattern. + */ + const auto& get_contact_pattern() const + { + return this->parameters.template get>(); + } + + /** + * @brief Returns a reference to the contact pattern. + * + * @return A reference to the contact pattern. + */ + auto& get_contact_pattern() + { + return this->parameters.template get>(); + } + + /** + * @brief Sets the contact pattern. + * + * @tparam T The type of the new contact pattern. + * @param contact_pattern The new contact pattern to be set. + */ + template + void set_contact_pattern(T contact_pattern) + { + this->parameters.template get>() = std::move(contact_pattern); + } + /** * serialize this. * @see mio::serialize diff --git a/cpp/models/ode_secirvvs/model.h b/cpp/models/ode_secirvvs/model.h index 932abb2b54..363faf6485 100644 --- a/cpp/models/ode_secirvvs/model.h +++ b/cpp/models/ode_secirvvs/model.h @@ -511,6 +511,38 @@ class Model } } + /** + * @brief Returns a const reference to the contact pattern. + * + * @return A const reference to the contact pattern. + */ + const auto& get_contact_pattern() const + { + return this->parameters.template get>(); + } + + /** + * @brief Returns a reference to the contact pattern. + * + * @return A reference to the contact pattern. + */ + auto& get_contact_pattern() + { + return this->parameters.template get>(); + } + + /** + * @brief Sets the contact pattern. + * + * @tparam T The type of the new contact pattern. + * @param contact_pattern The new contact pattern to be set. + */ + template + void set_contact_pattern(T contact_pattern) + { + this->parameters.template get>() = std::move(contact_pattern); + } + /** * serialize this. * @see mio::serialize diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index c883a434f5..3b3b329ab8 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -548,8 +548,8 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi for (int t_idx = 0; t_idx < num_days; ++t_idx) { auto t = mio::SimulationDay((size_t)t_idx); for (auto j = mio::AgeGroup(0); j < params.get_num_groups(); j++) { - mobility_model.parameters.template get>()[{j, t}] = 0; - mobility_model.parameters.template get>()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; // mobility_model.parameters.template get()[{j, t}] = 0; } } @@ -837,4 +837,4 @@ int main() } mio::mpi::finalize(); return 0; -} \ No newline at end of file +} From b95ea467e9f708f5a3c803f02768cc0afc84c0f1 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 13 Mar 2025 10:22:45 +0100 Subject: [PATCH 20/26] draw sample, 2022 example with secirts --- cpp/models/ode_secirts/parameter_space.h | 72 ++++ cpp/models/ode_secirvvs/parameter_space.h | 4 +- .../2022_omicron_late_phase_mobility.cpp | 340 +++++++++++------- cpp/simulations/CMakeLists.txt | 2 +- 4 files changed, 282 insertions(+), 136 deletions(-) diff --git a/cpp/models/ode_secirts/parameter_space.h b/cpp/models/ode_secirts/parameter_space.h index 0d73a09ea8..e6cb02caae 100644 --- a/cpp/models/ode_secirts/parameter_space.h +++ b/cpp/models/ode_secirts/parameter_space.h @@ -21,6 +21,7 @@ #define MIO_ODE_SECIRTS_PARAMETER_SPACE_H #include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/mobility/metapopulation_mobility_detailed.h" #include "memilio/utils/memory.h" #include "memilio/utils/logging.h" #include "memilio/utils/parameter_distributions.h" @@ -263,6 +264,77 @@ Graph, MobilityParameters> draw_sample(Graph, MobilityPa return sampled_graph; } +/** + * Draws samples for each model node in a graph. + * Some parameters are shared between nodes and only sampled once. + * @tparam FP floating point type, e.g., double + * @param graph Graph to be sampled. + * @return Graph with nodes and edges from the input graph sampled. + */ +template +ExtendedGraph> draw_sample(ExtendedGraph>& graph) +{ + ExtendedGraph> sampled_graph; + + //sample global parameters + auto& shared_params_base_model = graph.nodes()[0].property.base_sim; + auto& shared_mobility_model = graph.nodes()[0].property.mobility_sim; + draw_sample_infection(shared_params_base_model); + auto& shared_contacts = shared_params_base_model.parameters.template get>(); + shared_contacts.draw_sample_dampings(); + auto& shared_dynamic_npis = shared_params_base_model.parameters.template get>(); + shared_dynamic_npis.draw_sample(); + + for (auto& params_node : graph.nodes()) { + auto& node_model_local = params_node.property.base_sim; + + //sample local parameters + draw_sample_demographics(params_node.property.base_sim); + + //copy global parameters + //save parameters so they aren't overwritten + auto local_icu_capacity = node_model_local.parameters.template get>(); + auto local_tnt_capacity = node_model_local.parameters.template get>(); + auto local_holidays = node_model_local.parameters.template get>().get_school_holidays(); + auto local_daily_v1 = node_model_local.parameters.template get>(); + auto local_daily_v2 = node_model_local.parameters.template get>(); + auto local_daily_v3 = node_model_local.parameters.template get>(); + node_model_local.parameters = shared_params_base_model.parameters; + node_model_local.parameters.template get>() = local_icu_capacity; + node_model_local.parameters.template get>() = local_tnt_capacity; + node_model_local.parameters.template get>().get_school_holidays() = local_holidays; + node_model_local.parameters.template get>() = local_daily_v1; + node_model_local.parameters.template get>() = local_daily_v2; + node_model_local.parameters.template get>() = local_daily_v3; + + node_model_local.parameters.template get>().make_matrix(); + node_model_local.apply_constraints(); + + // do the same for the mobility model + auto& node_mobility_model = params_node.property.mobility_sim; + auto node_mobility_contacts = node_mobility_model.parameters.template get>(); + node_mobility_model.parameters = shared_mobility_model.parameters; + node_mobility_model.parameters.template get>() = node_mobility_contacts; + + // set vaccination parameters to zero + node_mobility_model.parameters.template get>().array().setConstant(0); + node_mobility_model.parameters.template get>().array().setConstant(0); + node_mobility_model.parameters.template get>().array().setConstant(0); + + node_mobility_model.apply_constraints(); + sampled_graph.add_node(params_node.id, node_model_local, node_mobility_model, + params_node.property.stay_duration); + } + + for (auto& edge : graph.edges()) { + auto edge_params = edge.property.get_parameters(); + sampled_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge_params, edge.property.travel_time, + edge.property.path); + } + + return sampled_graph; +} + } // namespace osecirts } // namespace mio diff --git a/cpp/models/ode_secirvvs/parameter_space.h b/cpp/models/ode_secirvvs/parameter_space.h index e43dc7aa3b..c73a99e195 100644 --- a/cpp/models/ode_secirvvs/parameter_space.h +++ b/cpp/models/ode_secirvvs/parameter_space.h @@ -230,7 +230,7 @@ Graph, MobilityParameters> draw_sample(Graph, MobilityPa * Some parameters are shared between nodes and only sampled once. * @tparam FP floating point type, e.g., double * @param graph Graph to be sampled. - * @param variant_high If true, use high value for infectiousness of variant. + * @param fact_mask_transport Factor to adjust the transmission probability on contact for the mobility model. * @return Graph with nodes and edges from the input graph sampled. */ template @@ -294,8 +294,6 @@ ExtendedGraph> draw_sample(ExtendedGraph>& graph, FP fact_ma for (auto& edge : graph.edges()) { auto edge_params = edge.property.get_parameters(); - //no dynamic NPIs - //TODO: add switch to optionally enable dynamic NPIs to edges sampled_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge_params, edge.property.travel_time, edge.property.path); } diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 3b3b329ab8..700ca376a0 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -26,9 +26,9 @@ #include "memilio/utils/date.h" #include "memilio/utils/miompi.h" #include "memilio/utils/random_number_generator.h" -#include "ode_secirvvs/parameters.h" -#include "ode_secirvvs/parameters_io.h" -#include "ode_secirvvs/parameter_space.h" +#include "ode_secirts/parameters.h" +#include "ode_secirts/parameters_io.h" +#include "ode_secirts/parameter_space.h" #include "memilio/mobility/metapopulation_mobility_instant.h" #include "memilio/mobility/metapopulation_mobility_detailed.h" #include "memilio/utils/stl_util.h" @@ -89,7 +89,7 @@ void array_assign_uniform_distribution(mio::CustomIndexArray set_covid_parameters(mio::osecirvvs::Parameters& params) +mio::IOResult set_covid_parameters(mio::osecirts::Parameters& params) { //times // doi.org/10.1016/j.lanepe.2022.100446 , doi.org/10.3201/eid2806.220158 @@ -108,15 +108,14 @@ mio::IOResult set_covid_parameters(mio::osecirvvs::Parameters& par const double timeInfectedCriticalMax[] = {10.57, 10.57, 10.57, 12.86, 13.23, 13.25}; // https://doi.org/10.1186/s12879-022-07971-6 - array_assign_uniform_distribution(params.get>(), timeExposedMin, - timeExposedMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), timeExposedMin, timeExposedMax); + array_assign_uniform_distribution(params.get>(), timeInfectedNoSymptomsMin, timeInfectedNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), timeInfectedSymptomsMin, timeInfectedSymptomsMax); - array_assign_uniform_distribution(params.get>(), timeInfectedSevereMin, + array_assign_uniform_distribution(params.get>(), timeInfectedSevereMin, timeInfectedSevereMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), timeInfectedCriticalMin, timeInfectedCriticalMax); //probabilities @@ -168,7 +167,6 @@ mio::IOResult set_covid_parameters(mio::osecirvvs::Parameters& par const double criticalPerSevereMax[] = {0.10 * fac_icu, 0.10 * fac_icu, 0.10 * fac_icu, 0.20 * fac_icu, 0.35 * fac_icu, 0.45 * fac_icu}; - // 61% weniger risiko zu sterben doi:10.1136/bmjgh-2023-0123 // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10347449/pdf/bmjgh-2023-012328.pdf const double fac_dead = 0.39; const double deathsPerCriticalMin[] = {fac_dead * 0.00, fac_dead * 0.00, fac_dead * 0.10, @@ -176,23 +174,11 @@ mio::IOResult set_covid_parameters(mio::osecirvvs::Parameters& par const double deathsPerCriticalMax[] = {fac_dead * 0.10, fac_dead * 0.10, fac_dead * 0.18, fac_dead * 0.18, fac_dead * 0.50, fac_dead * 0.7}; - // alternative https://doi.org/10.1136/bmj-2022-070695 - // const double fac_dead_u59 = 0.14; - // const double fac_dead_p59 = 0.44; - // const double deathsPerCriticalMin[] = {fac_dead_u59 * 0.00, fac_dead_u59 * 0.00, fac_dead_u59 * 0.10, - // fac_dead_u59 * 0.10, fac_dead_p59 * 0.30, fac_dead_p59 * 0.5}; // 2021 paper - // const double deathsPerCriticalMax[] = {fac_dead_u59 * 0.10, fac_dead_u59 * 0.10, fac_dead_u59 * 0.18, - // fac_dead_u59 * 0.18, fac_dead_p59 * 0.50, fac_dead_p59 * 0.7}; - - const double reducExposedPartialImmunityMin = 1.0; //0.569; // doi.org/10.1136/bmj-2022-071502 - const double reducExposedPartialImmunityMax = 1.0; // 0.637; // doi.org/10.1136/bmj-2022-071502 - // const double reducExposedImprovedImmunityMin = 0.36; // https://doi.org/10.1038/s41591-023-02219-5 - // const double reducExposedImprovedImmunityMax = 0.66; // https://doi.org/10.1038/s41591-023-02219-5 + const double reducExposedPartialImmunityMin = 1.0; + const double reducExposedPartialImmunityMax = 1.0; const double reducExposedImprovedImmunityMin = 1.0; - //0.34 * reducExposedPartialImmunityMin; // https://jamanetwork.com/journals/jama/fullarticle/2788487 0.19346 const double reducExposedImprovedImmunityMax = 1.0; - //0.34 * reducExposedPartialImmunityMax; // https://jamanetwork.com/journals/jama/fullarticle/2788487 0.21658 const double reducInfectedSymptomsPartialImmunityMin = 0.746; // doi.org/10.1056/NEJMoa2119451 const double reducInfectedSymptomsPartialImmunityMax = 0.961; // doi.org/10.1056/NEJMoa2119451 @@ -207,49 +193,65 @@ mio::IOResult set_covid_parameters(mio::osecirvvs::Parameters& par const double reducInfectedSevereCriticalDeadImprovedImmunityMax = 0.19; // doi.org/10.1136/bmj-2022-071502 const double reducTimeInfectedMild = 0.5; // doi.org/10.1101/2021.09.24.21263978 - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), relativeTransmissionNoSymptomsMin, relativeTransmissionNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), riskOfInfectionFromSymptomaticMin, riskOfInfectionFromSymptomaticMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), maxRiskOfInfectionFromSymptomaticMin, maxRiskOfInfectionFromSymptomaticMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), recoveredPerInfectedNoSymptomsMin, recoveredPerInfectedNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), severePerInfectedSymptomsMin, severePerInfectedSymptomsMax); - array_assign_uniform_distribution(params.get>(), criticalPerSevereMin, + array_assign_uniform_distribution(params.get>(), criticalPerSevereMin, criticalPerSevereMax); - array_assign_uniform_distribution(params.get>(), deathsPerCriticalMin, + array_assign_uniform_distribution(params.get>(), deathsPerCriticalMin, deathsPerCriticalMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), reducExposedPartialImmunityMin, reducExposedPartialImmunityMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), reducExposedImprovedImmunityMin, reducExposedImprovedImmunityMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), reducInfectedSymptomsPartialImmunityMin, reducInfectedSymptomsPartialImmunityMax); - array_assign_uniform_distribution(params.get>(), + array_assign_uniform_distribution(params.get>(), reducInfectedSymptomsImprovedImmunityMin, reducInfectedSymptomsImprovedImmunityMax); array_assign_uniform_distribution( - params.get>(), + params.get>(), reducInfectedSevereCriticalDeadPartialImmunityMin, reducInfectedSevereCriticalDeadPartialImmunityMax); array_assign_uniform_distribution( - params.get>(), + params.get>(), reducInfectedSevereCriticalDeadImprovedImmunityMin, reducInfectedSevereCriticalDeadImprovedImmunityMax); - array_assign_uniform_distribution(params.get>(), - reducTimeInfectedMild, reducTimeInfectedMild); + array_assign_uniform_distribution(params.get>(), reducTimeInfectedMild, + reducTimeInfectedMild); //sasonality const double seasonality_min = 0.1; const double seasonality_max = 0.3; - assign_uniform_distribution(params.get>(), seasonality_min, seasonality_max); + assign_uniform_distribution(params.get>(), seasonality_min, seasonality_max); // Delta specific parameter - params.get() = mio::get_day_in_year(mio::Date(2021, 6, 6)); + params.get() = mio::get_day_in_year(mio::Date(2022, 6, 6)); + + const double ImmunityInterval1Min = 60; ///https://doi.org/10.1016/S1473-3099(22)00801-5 + const double ImmunityInterval1Max = 60; + + array_assign_uniform_distribution(params.get>(), + ImmunityInterval1Min, ImmunityInterval1Max); + + const double ImmunityInterval2Min = 60; // https://doi.org/10.1016/S1473-3099(22)00801-5 + const double ImmunityInterval2Max = 60; + + array_assign_uniform_distribution(params.get>(), + ImmunityInterval2Min, ImmunityInterval2Max); + + // https://doi.org/10.1016/S1473-3099(22)00801-5 + params.get>() = 365.0; + params.get>() = 365.0; return mio::success(); } @@ -299,7 +301,7 @@ static const std::map contact_locations = {{Contac * @param params Object that the contact matrices will be added to. * @returns any io errors that happen during reading of the files. */ -mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirvvs::Parameters& params, +mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirts::Parameters& params, ScalarType avg_tt = 0.0, ScalarType share_staying = 1.0) { auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); @@ -331,12 +333,12 @@ mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirvv contact_matrices[size_t(contact_location.first)].get_baseline() = baseline_adjusted; contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); } - params.get>() = mio::UncertainContactMatrix(contact_matrices); + params.get>() = mio::UncertainContactMatrix(contact_matrices); return mio::success(); } -mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirvvs::Parameters& params) +mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirts::Parameters& params) { auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); auto contact_matrix_transport = contact_transport_status.value(); @@ -346,12 +348,12 @@ mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio contact_matrices[0].get_baseline() = contact_matrix_transport; contact_matrices[0].get_minimum() = Eigen::MatrixXd::Zero(6, 6); - params.get>() = mio::UncertainContactMatrix(contact_matrices); + params.get>() = mio::UncertainContactMatrix(contact_matrices); return mio::success(); } -mio::IOResult scale_contacts_local(mio::ExtendedGraph>& params_graph, +mio::IOResult scale_contacts_local(mio::ExtendedGraph>& params_graph, const fs::path& data_dir, const std::string mobility_data_dir, const std::vector commuting_weights) { @@ -395,7 +397,7 @@ mio::IOResult scale_contacts_local(mio::ExtendedGraph, mio::MobilityParameters>& graph, +void init_pop_cologne_szenario(mio::Graph, mio::MobilityParameters>& graph, const int id_cologne) { std::vector> immunity = {{0.04, 0.61, 0.35}, {0.04, 0.61, 0.35}, {0.075, 0.62, 0.305}, @@ -404,34 +406,33 @@ void init_pop_cologne_szenario(mio::Graph, mio::Mo for (auto& node : graph.nodes()) { for (auto age = mio::AgeGroup(0); age < mio::AgeGroup(6); age++) { auto pop_age = 0.0; - for (auto inf_state = mio::Index(0); - inf_state < mio::osecirvvs::InfectionState::Count; ++inf_state) { + for (auto inf_state = mio::Index(0); + inf_state < mio::osecirts::InfectionState::Count; ++inf_state) { pop_age += node.property.populations[{age, inf_state}]; node.property.populations[{age, inf_state}] = 0.0; } size_t immunity_index = static_cast(age); - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] = pop_age * immunity[immunity_index][0]; - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] = pop_age * immunity[immunity_index][1]; - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] = pop_age * immunity[immunity_index][2]; } if (node.id == id_cologne) { // infect p% of population ScalarType p = 0.05; for (auto age = mio::AgeGroup(0); age < graph.nodes()[0].property.parameters.get_num_groups(); age++) { - node.property.populations[{mio::AgeGroup(age), mio::osecirvvs::InfectionState::InfectedSymptomsNaive}] = - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] * p; - node.property.populations[{age, mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity}] = - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] * p; + node.property.populations[{mio::AgeGroup(age), mio::osecirts::InfectionState::InfectedSymptomsNaive}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] * p; + node.property.populations[{age, mio::osecirts::InfectionState::InfectedSymptomsPartialImmunity}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] * p; node.property.populations[{mio::AgeGroup(age), - mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}] = - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] * p; - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleNaive}] *= (1 - p); - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptiblePartialImmunity}] *= (1 - p); - node.property.populations[{age, mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity}] *= - (1 - p); + mio::osecirts::InfectionState::InfectedSymptomsImprovedImmunity}] = + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] * p; + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] *= (1 - p); + node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] *= (1 - p); + node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] *= (1 - p); } } } @@ -459,39 +460,39 @@ void init_pop_cologne_szenario(mio::Graph, mio::Mo */ template mio::IOResult -set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mio::Date end_date, +set_nodes(const mio::osecirts::Parameters& params, mio::Date start_date, mio::Date end_date, const fs::path& data_dir, const std::string& population_data_path, const std::string& stay_times_data_path, - bool is_node_for_county, mio::ExtendedGraph>& params_graph, - ReadFunction&& read_func, NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, - FP scaling_factor_icu, FP tnt_capacity_factor, int num_days = 0, bool export_time_series = false, - bool rki_age_groups = true, bool masks = false, bool ffp2 = false) + bool is_node_for_county, mio::ExtendedGraph>& params_graph, ReadFunction&& read_func, + NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, FP scaling_factor_icu, + FP tnt_capacity_factor, const std::vector> immunity_population, int num_days = 0, + bool export_time_series = false, bool rki_age_groups = true, bool masks = false, bool ffp2 = false) { BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county, rki_age_groups)); - std::vector> nodes(node_ids.size(), - mio::osecirvvs::Model(int(size_t(params.get_num_groups())))); + std::vector> nodes(node_ids.size(), + mio::osecirts::Model(int(size_t(params.get_num_groups())))); for (auto& node : nodes) { node.parameters = params; } BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), - num_days, export_time_series)); + num_days, immunity_population, export_time_series)); for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; //local parameters - auto& tnt_value = nodes[node_idx].parameters.template get>(); + auto& tnt_value = nodes[node_idx].parameters.template get>(); tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); //holiday periods auto id = int(mio::regions::CountyId(node_ids[node_idx])); auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); - auto& contacts = nodes[node_idx].parameters.template get>(); + auto& contacts = nodes[node_idx].parameters.template get>(); contacts.get_school_holidays() = std::vector>(holiday_periods.size()); std::transform( @@ -502,8 +503,8 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi //uncertainty in populations for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { - for (auto j = mio::Index::Compartments>(0); - j < mio::osecirvvs::Model::Compartments::Count; ++j) { + for (auto j = mio::Index::Compartments>(0); + j < mio::osecirts::Model::Compartments::Count; ++j) { auto& compartment_value = nodes[node_idx].populations[{i, j}]; compartment_value = mio::UncertainValue(0.5 * (1.1 * double(compartment_value) + 0.9 * double(compartment_value))); @@ -517,20 +518,20 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi mobility_model.populations.set_total(0); // reduce transmission on contact due to mask obligation in mobility node - // first age group not able to (properly) wear masks if (masks) { const double fact_surgical_mask = 0.1; const double fact_ffp2 = 0.001; + // first age group not able to (properly) wear masks, second age group only partially double factor_mask[] = {1, fact_surgical_mask, fact_ffp2, fact_ffp2, fact_ffp2, fact_ffp2}; if (!ffp2) { - for (size_t j = 0; j < 6; j++) { + for (size_t j = 2; j < 6; j++) { factor_mask[j] = factor_mask[j] * fact_surgical_mask / fact_ffp2; } } - double fac_variant = 1.45; //1.94; //https://doi.org/10.7554/eLife.78933 + double fac_variant = 1.94; //https://doi.org/10.7554/eLife.78933 double transmissionProbabilityOnContactMin[] = {0.02 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, 0.08 * fac_variant, 0.1 * fac_variant}; @@ -541,16 +542,17 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi transmissionProbabilityOnContactMax[i] = transmissionProbabilityOnContactMax[i] * factor_mask[i]; } array_assign_uniform_distribution( - mobility_model.parameters.template get>(), + mobility_model.parameters.template get>(), transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); } + // no vaccination in mobility node for (int t_idx = 0; t_idx < num_days; ++t_idx) { auto t = mio::SimulationDay((size_t)t_idx); for (auto j = mio::AgeGroup(0); j < params.get_num_groups(); j++) { - mobility_model.parameters.template get>()[{j, t}] = 0; - mobility_model.parameters.template get>()[{j, t}] = 0; - // mobility_model.parameters.template get()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; + mobility_model.parameters.template get>()[{j, t}] = 0; } } @@ -573,8 +575,8 @@ set_nodes(const mio::osecirvvs::Parameters& params, mio::Date start_date, mi */ mio::IOResult set_edges(const std::string& travel_times_dir, const std::string mobility_data_dir, const std::string& travel_path_dir, - mio::ExtendedGraph>& params_graph, - std::initializer_list& migrating_compartments, size_t contact_locations_size, + mio::ExtendedGraph>& params_graph, + std::initializer_list& migrating_compartments, size_t contact_locations_size, std::vector commuting_weights = std::vector{}, ScalarType theshold_edges = 4e-5) { BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); @@ -634,38 +636,42 @@ set_edges(const std::string& travel_times_dir, const std::string mobility_data_d * @param data_dir data directory. * @returns created graph or any io errors that happen during reading of the files. */ -mio::IOResult>> get_graph(const mio::Date start_date, - const mio::Date end_date, const int num_days, - const std::string& data_dir, bool masks, - bool ffp2, bool szenario_cologne, bool edges) +mio::IOResult>> get_graph(const mio::Date start_date, + const mio::Date end_date, const int num_days, + const std::string& data_dir, bool masks, + bool ffp2, bool szenario_cologne, bool edges) { mio::unused(szenario_cologne); std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); + const std::vector> immunity_population = {{0.04, 0.04, 0.075, 0.08, 0.035, 0.01}, + {0.61, 0.61, 0.62, 0.62, 0.58, 0.41}, + {0.35, 0.35, 0.305, 0.3, 0.385, 0.58}}; + // global parameters const int num_age_groups = 6; - mio::osecirvvs::Parameters params(num_age_groups); - params.get() = mio::get_day_in_year(start_date); - auto params_status = set_covid_parameters(params); - auto contacts_status = set_contact_matrices(data_dir, params); - params.get() = mio::get_day_in_year(start_date); + mio::osecirts::Parameters params(num_age_groups); + params.get() = mio::get_day_in_year(start_date); + auto params_status = set_covid_parameters(params); + auto contacts_status = set_contact_matrices(data_dir, params); + params.get() = mio::get_day_in_year(start_date); // create graph - mio::ExtendedGraph> params_graph; + mio::ExtendedGraph> params_graph; // set nodes - auto scaling_factor_infected = std::vector(size_t(params.get_num_groups()), 1.0); + auto scaling_factor_infected = std::vector(size_t(params.get_num_groups()), 1.7); auto scaling_factor_icu = 1.0; auto tnt_capacity_factor = 1.43 / 100000.; - const auto& read_function_nodes = mio::osecirvvs::read_input_data_county>; + const auto& read_function_nodes = mio::osecirts::read_input_data_county>; const auto& node_id_function = mio::get_node_ids; auto set_nodes_status = set_nodes( params, start_date, end_date, data_dir, mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"), durations_dir, true, params_graph, read_function_nodes, node_id_function, scaling_factor_infected, scaling_factor_icu, - tnt_capacity_factor, num_days, false, true, ffp2, masks); + tnt_capacity_factor, immunity_population, num_days, false, true, ffp2, masks); if (!set_nodes_status) { return set_nodes_status.error(); @@ -673,23 +679,23 @@ mio::IOResult>> get_graph(const // iterate over all nodes and set the contact_matrix for the mobility models for (auto& node : params_graph.nodes()) { - BOOST_OUTCOME_TRY(set_contact_matrices_transport(data_dir, node.property.base_sim.parameters)); + BOOST_OUTCOME_TRY(set_contact_matrices_transport(data_dir, node.property.mobility_sim.parameters)); } // set edges if (edges) { - auto migrating_compartments = {mio::osecirvvs::InfectionState::SusceptibleNaive, - mio::osecirvvs::InfectionState::ExposedNaive, - mio::osecirvvs::InfectionState::InfectedNoSymptomsNaive, - mio::osecirvvs::InfectionState::InfectedSymptomsNaive, - mio::osecirvvs::InfectionState::SusceptibleImprovedImmunity, - mio::osecirvvs::InfectionState::SusceptiblePartialImmunity, - mio::osecirvvs::InfectionState::ExposedPartialImmunity, - mio::osecirvvs::InfectionState::InfectedNoSymptomsPartialImmunity, - mio::osecirvvs::InfectionState::InfectedSymptomsPartialImmunity, - mio::osecirvvs::InfectionState::ExposedImprovedImmunity, - mio::osecirvvs::InfectionState::InfectedNoSymptomsImprovedImmunity, - mio::osecirvvs::InfectionState::InfectedSymptomsImprovedImmunity}; + auto migrating_compartments = {mio::osecirts::InfectionState::SusceptibleNaive, + mio::osecirts::InfectionState::ExposedNaive, + mio::osecirts::InfectionState::InfectedNoSymptomsNaive, + mio::osecirts::InfectionState::InfectedSymptomsNaive, + mio::osecirts::InfectionState::SusceptibleImprovedImmunity, + mio::osecirts::InfectionState::SusceptiblePartialImmunity, + mio::osecirts::InfectionState::ExposedPartialImmunity, + mio::osecirts::InfectionState::InfectedNoSymptomsPartialImmunity, + mio::osecirts::InfectionState::InfectedSymptomsPartialImmunity, + mio::osecirts::InfectionState::ExposedImprovedImmunity, + mio::osecirts::InfectionState::InfectedNoSymptomsImprovedImmunity, + mio::osecirts::InfectionState::InfectedSymptomsImprovedImmunity}; auto set_edges_status = set_edges( travel_times_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), mio::path_join(data_dir, "mobility", "wegketten_ohne_komma.txt"), params_graph, migrating_compartments, @@ -710,9 +716,8 @@ mio::IOResult>> get_graph(const mio::IOResult run(const std::string data_dir, std::string res_dir, const int num_runs, const int num_days) { // mio::set_log_level(mio::LogLevel::critical); - auto start_date = mio::Date(2021, 8, 1); - auto end_date = mio::Date(2021, 11, 1); - // const int num_runs = 12; + auto start_date = mio::Date(2022, 8, 1); + auto end_date = mio::Date(2022, 11, 1); constexpr bool masks = true; constexpr bool ffp2 = true; const bool edges = true; @@ -736,7 +741,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i // check if res_dir dir exist, otherwise create if (!fs::exists(res_dir)) { - fs::create_directory(res_dir); + fs::create_directories(res_dir); } std::vector county_ids(params_graph.nodes().size()); @@ -744,13 +749,13 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i return n.id; }); - // mio::ExtendedGraph>> + // mio::ExtendedGraph>> // parameter study auto parameter_study = mio::ParameterStudy< - mio::osecirvvs::Simulation>>, - mio::ExtendedGraph>, + mio::osecirts::Simulation>>, + mio::ExtendedGraph>, mio::ExtendedGraph< - mio::osecirvvs::Simulation>>>>( + mio::osecirts::Simulation>>>>( params_graph, 0.0, num_days, 0.01, num_runs); if (mio::mpi::is_root()) { printf("Seeds: "); @@ -762,7 +767,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i auto save_single_run_result = mio::IOResult(mio::success()); auto ensemble = parameter_study.run( [&](auto&& graph) { - return draw_sample(graph, 1.0); + return draw_sample(graph); }, [&](auto results_graph, auto&& run_idx) { std::vector> interpolated_result; @@ -772,7 +777,7 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i return interpolate_simulation_result(n.property.base_sim.get_result()); }); - auto params = std::vector>(); + auto params = std::vector>(); params.reserve(results_graph.nodes().size()); std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), [](auto&& node) { @@ -788,53 +793,124 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i return interpolated_flows; }); + // same for the mobility model (flows are enough here) + auto params_mobility = std::vector>(); + params_mobility.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), + std::back_inserter(params_mobility), [](auto&& node) { + return node.property.mobility_sim.get_model(); + }); + + auto flows_mobility = std::vector>{}; + flows_mobility.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), + std::back_inserter(flows_mobility), [](auto&& node) { + auto& flow_node = node.property.mobility_sim.get_flows(); + auto interpolated_flows = mio::interpolate_simulation_result(flow_node); + return interpolated_flows; + }); + std::cout << "Run " << run_idx << " complete." << std::endl; - return std::make_tuple(interpolated_result, params, flows); + return std::make_tuple(interpolated_result, params, flows, params_mobility, flows_mobility); }); if (ensemble.size() > 0) { auto ensemble_results = std::vector>>{}; ensemble_results.reserve(ensemble.size()); - auto ensemble_params = std::vector>>{}; + auto ensemble_params = std::vector>>{}; ensemble_params.reserve(ensemble.size()); auto ensemble_flows = std::vector>>{}; ensemble_flows.reserve(ensemble.size()); + auto ensemble_params_mobility = std::vector>>{}; + ensemble_params_mobility.reserve(ensemble.size()); + auto ensemble_flows_mobility = std::vector>>{}; + ensemble_flows_mobility.reserve(ensemble.size()); for (auto&& run : ensemble) { ensemble_results.emplace_back(std::move(std::get<0>(run))); ensemble_params.emplace_back(std::move(std::get<1>(run))); ensemble_flows.emplace_back(std::move(std::get<2>(run))); + ensemble_params_mobility.emplace_back(std::move(std::get<3>(run))); + ensemble_flows_mobility.emplace_back(std::move(std::get<4>(run))); } - BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); + BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); auto result_dir_run_flows = res_dir + "/flows"; if (mio::mpi::is_root()) { boost::filesystem::create_directories(result_dir_run_flows); printf("Saving Flow results to \"%s\".\n", result_dir_run_flows.c_str()); } BOOST_OUTCOME_TRY(save_results(ensemble_flows, ensemble_params, county_ids, result_dir_run_flows, false)); + + auto result_dir_mobility = res_dir + "/mobility"; + auto result_dir_run_flows_mobility = result_dir_mobility + "/flows"; + if (mio::mpi::is_root()) { + boost::filesystem::create_directories(result_dir_run_flows_mobility); + printf("Saving Flow results to \"%s\".\n", result_dir_run_flows_mobility.c_str()); + } + BOOST_OUTCOME_TRY(save_results(ensemble_flows_mobility, ensemble_params_mobility, county_ids, + result_dir_run_flows_mobility, false)); } return mio::success(); } -int main() +int main(int argc, char** argv) { mio::set_log_level(mio::LogLevel::warn); mio::mpi::init(); - const std::string memilio_dir = "/localdata1/pure/memilio"; - const std::string data_dir = mio::path_join(memilio_dir, "data"); //"/localdata1/code/memilio/data"; - std::string results_dir = mio::path_join(memilio_dir, "results"); + std::string data_dir = ""; + std::string results_dir = ""; + int num_days = 10; + int num_runs = 2; + + if (argc == 5) { + // Full specification: + data_dir = argv[1]; + results_dir = argv[2]; + num_runs = atoi(argv[3]); + num_days = atoi(argv[4]); + + if (mio::mpi::is_root()) { + printf("Reading data from \"%s\", saving results to \"%s\".\n", data_dir.c_str(), results_dir.c_str()); + printf("Number of runs: %d, Number of days: %d.\n", num_runs, num_days); + } + } + else if (argc == 3) { + // Partial specification: , use defaults for num_runs and num_days + data_dir = argv[1]; + results_dir = argv[2]; + + if (mio::mpi::is_root()) { + printf("Reading data from \"%s\", saving results to \"%s\".\n", data_dir.c_str(), results_dir.c_str()); + printf("Using default values - Number of runs: %d, Number of days: %d.\n", num_runs, num_days); + } + } + else { + if (mio::mpi::is_root()) { + printf("Usage:\n"); + printf("2022_omicron_late_phase_mobility \n"); + printf("\tRun simulation with data from , saving results to .\n"); + printf("\t: Number of simulation runs.\n"); + printf("\t: Number of days to simulate.\n"); + printf("2022_omicron_late_phase_mobility \n"); + printf("\tRun simulation with default num_runs=%d and num_days=%d.\n", num_runs, num_days); + } + mio::mpi::finalize(); + return 0; + } - const auto num_days = 90; - const auto num_runs = 10; - auto result = run(data_dir, results_dir, num_runs, num_days); + // Run the simulation + auto result = run(data_dir, results_dir, num_runs, num_days); if (!result) { - printf("%s\n", result.error().formatted_message().c_str()); + if (mio::mpi::is_root()) { + printf("%s\n", result.error().formatted_message().c_str()); + } mio::mpi::finalize(); return -1; } + mio::mpi::finalize(); return 0; } diff --git a/cpp/simulations/CMakeLists.txt b/cpp/simulations/CMakeLists.txt index 9573a4710b..3d85986539 100644 --- a/cpp/simulations/CMakeLists.txt +++ b/cpp/simulations/CMakeLists.txt @@ -8,7 +8,7 @@ if(MEMILIO_HAS_JSONCPP AND MEMILIO_HAS_HDF5) target_compile_options(2021_vaccination_delta PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) add_executable(2022_omicron_late_phase_mobility 2022_omicron_late_phase_mobility.cpp) - target_link_libraries(2022_omicron_late_phase_mobility PRIVATE memilio ode_secirvvs Boost::filesystem ${HDF5_C_LIBRARIES}) + target_link_libraries(2022_omicron_late_phase_mobility PRIVATE memilio ode_secirts Boost::filesystem ${HDF5_C_LIBRARIES}) target_compile_options(2022_omicron_late_phase_mobility PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) add_executable(abm_simulation abm.cpp) From 1a22595a0ca4cd02b999e1d4fb5a4115af8a6a89 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:14:18 +0100 Subject: [PATCH 21/26] improve doc for simulation --- .../2022_omicron_late_phase_mobility.cpp | 526 ++++++++++-------- 1 file changed, 288 insertions(+), 238 deletions(-) diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index 700ca376a0..cc25c272b3 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -39,44 +39,44 @@ namespace fs = boost::filesystem; /** - * Set a value and distribution of an UncertainValue. - * Assigns average of min and max as a value and UNIFORM(min, max) as a distribution. - * @param p uncertain value to set. - * @param min minimum of distribution. - * @param max minimum of distribution. + * Assigns a uniform distribution to an UncertainValue with a specified range. + * The value is set to the average of min and max, and the distribution is UNIFORM(min, max). + * @param[in,out] param UncertainValue to configure. + * @param[in] min Lower bound of the uniform distribution. + * @param[in] max Upper bound of the uniform distribution. */ -void assign_uniform_distribution(mio::UncertainValue& p, double min, double max) +void assign_uniform_distribution(mio::UncertainValue& param, double min, double max) { - p = mio::UncertainValue(0.5 * (max + min)); - p.set_distribution(mio::ParameterDistributionUniform(min, max)); + param = mio::UncertainValue(0.5 * (min + max)); + param.set_distribution(mio::ParameterDistributionUniform(min, max)); } /** - * Set a value and distribution of an array of UncertainValues. - * Assigns average of min[i] and max[i] as a value and UNIFORM(min[i], max[i]) as a distribution for - * each element i of the array. - * @param array array of UncertainValues to set. - * @param min minimum of distribution for each element of array. - * @param max minimum of distribution for each element of array. + * Assigns uniform distributions to an array of UncertainValues. + * Each element i is set to the average of min[i] and max[i] with a UNIFORM(min[i], max[i]) distribution. + * @param[in,out] array Array of UncertainValues to configure. + * @param[in] min Array of lower bounds for each element. + * @param[in] max Array of upper bounds for each element. + * @tparam N Size of the array, must match the number of elements in min and max. */ template -void array_assign_uniform_distribution(mio::CustomIndexArray, mio::AgeGroup>& array, +void assign_uniform_distribution_array(mio::CustomIndexArray, mio::AgeGroup>& array, const double (&min)[N], const double (&max)[N]) { - assert(N == array.numel()); + assert(N == array.numel() && "Array size must match the number of elements in min and max."); for (auto i = mio::AgeGroup(0); i < mio::AgeGroup(N); ++i) { - assign_uniform_distribution(array[i], min[size_t(i)], max[size_t(i)]); + assign_uniform_distribution(array[i], min[static_cast(i)], max[static_cast(i)]); } } /** - * Set a value and distribution of an array of UncertainValues. - * Assigns average of min and max as a value and UNIFORM(min, max) as a distribution to every element of the array. - * @param array array of UncertainValues to set. - * @param min minimum of distribution. - * @param max minimum of distribution. + * Assigns a uniform distribution to all elements of an array of UncertainValues using a single range. + * Each element is set to the average of min and max with a UNIFORM(min, max) distribution. + * @param[in,out] array Array of UncertainValues to configure. + * @param[in] min Lower bound of the uniform distribution applied to all elements. + * @param[in] max Upper bound of the uniform distribution applied to all elements. */ -void array_assign_uniform_distribution(mio::CustomIndexArray, mio::AgeGroup>& array, +void assign_uniform_distribution_array(mio::CustomIndexArray, mio::AgeGroup>& array, double min, double max) { for (auto i = mio::AgeGroup(0); i < array.size(); ++i) { @@ -85,171 +85,173 @@ void array_assign_uniform_distribution(mio::CustomIndexArray indicating success (currently no failure cases defined). */ mio::IOResult set_covid_parameters(mio::osecirts::Parameters& params) { - //times - // doi.org/10.1016/j.lanepe.2022.100446 , doi.org/10.3201/eid2806.220158 - const double timeExposedMin = 1.66; - const double timeExposedMax = 1.66; - const double timeInfectedNoSymptomsMin = 1.44; - const double timeInfectedNoSymptomsMax = 1.44; - - const double timeInfectedSymptomsMin = 6.58; //https://doi.org/10.1016/S0140-6736(22)00327-0 - const double timeInfectedSymptomsMax = 7.16; //https://doi.org/10.1016/S0140-6736(22)00327-0 - const double timeInfectedSevereMin[] = {1.8, 1.8, 1.8, 2.5, 3.5, 4.91}; // doi.org/10.1186/s12879-022-07971-6 - const double timeInfectedSevereMax[] = {2.3, 2.3, 2.3, 3.67, 5, 7.01}; // doi.org/10.1186/s12879-022-07971-6 - - const double timeInfectedCriticalMin[] = {9.29, 9.29, 9.29, - 10.842, 11.15, 11.07}; // https://doi.org/10.1186/s12879-022-07971-6 - const double timeInfectedCriticalMax[] = {10.57, 10.57, 10.57, - 12.86, 13.23, 13.25}; // https://doi.org/10.1186/s12879-022-07971-6 - - array_assign_uniform_distribution(params.get>(), timeExposedMin, timeExposedMax); - array_assign_uniform_distribution(params.get>(), - timeInfectedNoSymptomsMin, timeInfectedNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), - timeInfectedSymptomsMin, timeInfectedSymptomsMax); - array_assign_uniform_distribution(params.get>(), timeInfectedSevereMin, - timeInfectedSevereMax); - array_assign_uniform_distribution(params.get>(), - timeInfectedCriticalMin, timeInfectedCriticalMax); - - //probabilities - double fac_variant = 1.5; //https://doi.org/10.7554/eLife.78933 - const double transmissionProbabilityOnContactMin[] = {0.02 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, - 0.05 * fac_variant, 0.08 * fac_variant, 0.1 * fac_variant}; - - const double transmissionProbabilityOnContactMax[] = {0.04 * fac_variant, 0.07 * fac_variant, 0.07 * fac_variant, - 0.07 * fac_variant, 0.10 * fac_variant, 0.15 * fac_variant}; - - const double relativeTransmissionNoSymptomsMin = 0.5; - - //{0.6, 0.55, 0.65,0.7, 0.75, 0.85}; // DOI: 10.1097/INF.0000000000003791 - const double relativeTransmissionNoSymptomsMax = 0.5; - // {0.8, 0.75, 0.8,0.8, 0.825, 0.9}; // DOI: 10.1097/INF.0000000000003791 - const double riskOfInfectionFromSymptomaticMin = 0.0; // beta (depends on incidence and test and trace capacity) - const double riskOfInfectionFromSymptomaticMax = 0.2; - const double maxRiskOfInfectionFromSymptomaticMin = 0.4; - const double maxRiskOfInfectionFromSymptomaticMax = 0.5; - - // DOI: 10.1097/INF.0000000000003791 geht hier auch. aber ähnliche werte - const double recoveredPerInfectedNoSymptomsMin[] = {0.2, 0.25, 0.2, - 0.2, 0.175, 0.1}; // doi.org/10.1101/2022.05.05.22274697 - const double recoveredPerInfectedNoSymptomsMax[] = {0.4, 0.45, 0.35, - 0.3, 0.25, 0.15}; // doi.org/10.1101/2022.05.05.22274697 - - // 56% weniger risiko ins krankenhaus doi:10.1136/bmjgh-2023-0123 - // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10347449/pdf/bmjgh-2023-012328.pdf - // alternativ: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9321237/pdf/vaccines-10-01049.pdf - - // Faktoren aus https://www.thelancet.com/journals/lancet/article/PIIS0140-6736(22)00462-7/fulltext - const double severePerInfectedSymptomsMin[] = {1 * 0.006, 0.8 * 0.006, 0.4 * 0.015, - 0.3 * 0.049, 0.25 * 0.15, 0.35 * 0.2}; // 2021 paper - const double severePerInfectedSymptomsMax[] = {1 * 0.009, 0.8 * 0.009, 0.4 * 0.023, 0.3 * 0.074, - 0.25 * 0.18, 0.35 * 0.25}; // quelle 2021 paper + factors - - // const double criticalPerSevereMin[] = { - // 0.0511, 0.0686, 0.0491, 0.114, - // 0.1495, 0.0674}; // www.sozialministerium.at/dam/jcr:f472e977-e1bf-415f-95e1-35a1b53e608d/Factsheet_Coronavirus_Hospitalisierungen.pdf - // const double criticalPerSevereMax[] = { - // 0.0511, 0.0686, 0.0491, 0.114, - // 0.1495, 0.0674}; // www.sozialministerium.at/dam/jcr:f472e977-e1bf-415f-95e1-35a1b53e608d/Factsheet_Coronavirus_Hospitalisierungen.pdf - - // delta paper - // risk of icu admission https://doi.org/10.1177/14034948221108548 - const double fac_icu = 0.52; - const double criticalPerSevereMin[] = {0.05 * fac_icu, 0.05 * fac_icu, 0.05 * fac_icu, - 0.10 * fac_icu, 0.25 * fac_icu, 0.35 * fac_icu}; - const double criticalPerSevereMax[] = {0.10 * fac_icu, 0.10 * fac_icu, 0.10 * fac_icu, - 0.20 * fac_icu, 0.35 * fac_icu, 0.45 * fac_icu}; - - // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10347449/pdf/bmjgh-2023-012328.pdf - const double fac_dead = 0.39; - const double deathsPerCriticalMin[] = {fac_dead * 0.00, fac_dead * 0.00, fac_dead * 0.10, - fac_dead * 0.10, fac_dead * 0.30, fac_dead * 0.5}; // 2021 paper - const double deathsPerCriticalMax[] = {fac_dead * 0.10, fac_dead * 0.10, fac_dead * 0.18, - fac_dead * 0.18, fac_dead * 0.50, fac_dead * 0.7}; - - const double reducExposedPartialImmunityMin = 1.0; - const double reducExposedPartialImmunityMax = 1.0; - - const double reducExposedImprovedImmunityMin = 1.0; - const double reducExposedImprovedImmunityMax = 1.0; - - const double reducInfectedSymptomsPartialImmunityMin = 0.746; // doi.org/10.1056/NEJMoa2119451 - const double reducInfectedSymptomsPartialImmunityMax = 0.961; // doi.org/10.1056/NEJMoa2119451 - const double reducInfectedSymptomsImprovedImmunityMin = 0.295; // doi.org/10.1056/NEJMoa2119451 - const double reducInfectedSymptomsImprovedImmunityMax = 0.344; // doi.org/10.1056/NEJMoa2119451 - - const double reducInfectedSevereCriticalDeadPartialImmunityMin = - 0.52; // www.assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1050721/Vaccine-surveillance-report-week-4.pdf - const double reducInfectedSevereCriticalDeadPartialImmunityMax = - 0.82; // www.assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1050721/Vaccine-surveillance-report-week-4.pdf - const double reducInfectedSevereCriticalDeadImprovedImmunityMin = 0.1; // doi.org/10.1136/bmj-2022-071502 - const double reducInfectedSevereCriticalDeadImprovedImmunityMax = 0.19; // doi.org/10.1136/bmj-2022-071502 - const double reducTimeInfectedMild = 0.5; // doi.org/10.1101/2021.09.24.21263978 - - array_assign_uniform_distribution(params.get>(), - transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); - array_assign_uniform_distribution(params.get>(), - relativeTransmissionNoSymptomsMin, relativeTransmissionNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), - riskOfInfectionFromSymptomaticMin, riskOfInfectionFromSymptomaticMax); - array_assign_uniform_distribution(params.get>(), - maxRiskOfInfectionFromSymptomaticMin, maxRiskOfInfectionFromSymptomaticMax); - array_assign_uniform_distribution(params.get>(), - recoveredPerInfectedNoSymptomsMin, recoveredPerInfectedNoSymptomsMax); - array_assign_uniform_distribution(params.get>(), - severePerInfectedSymptomsMin, severePerInfectedSymptomsMax); - array_assign_uniform_distribution(params.get>(), criticalPerSevereMin, - criticalPerSevereMax); - array_assign_uniform_distribution(params.get>(), deathsPerCriticalMin, - deathsPerCriticalMax); - - array_assign_uniform_distribution(params.get>(), - reducExposedPartialImmunityMin, reducExposedPartialImmunityMax); - array_assign_uniform_distribution(params.get>(), - reducExposedImprovedImmunityMin, reducExposedImprovedImmunityMax); - array_assign_uniform_distribution(params.get>(), - reducInfectedSymptomsPartialImmunityMin, reducInfectedSymptomsPartialImmunityMax); - array_assign_uniform_distribution(params.get>(), - reducInfectedSymptomsImprovedImmunityMin, - reducInfectedSymptomsImprovedImmunityMax); - array_assign_uniform_distribution( + constexpr size_t num_age_groups = 6; + + // --- Transition Times --- + // Incubation and infectious periods sourced from literature + // Sources: doi.org/10.1016/j.lanepe.2022.100446, doi.org/10.3201/eid2806.220158 + const double time_exposed_min = 1.66; + const double time_exposed_max = 1.66; + const double time_infected_no_symptoms_min = 1.44; + const double time_infected_no_symptoms_max = 1.44; + + // Symptomatic period: doi.org/10.1016/S0140-6736(22)00327-0 + const double time_infected_symptoms_min = 6.58; + const double time_infected_symptoms_max = 7.16; + + // Severe and critical periods: doi.org/10.1186/s12879-022-07971-6 + const double time_infected_severe_min[num_age_groups] = {1.8, 1.8, 1.8, 2.5, 3.5, 4.91}; + const double time_infected_severe_max[num_age_groups] = {2.3, 2.3, 2.3, 3.67, 5, 7.01}; + const double time_infected_critical_min[num_age_groups] = {9.29, 9.29, 9.29, 10.842, 11.15, 11.07}; + const double time_infected_critical_max[num_age_groups] = {10.57, 10.57, 10.57, 12.86, 13.23, 13.25}; + + assign_uniform_distribution_array(params.get>(), time_exposed_min, + time_exposed_max); + assign_uniform_distribution_array(params.get>(), + time_infected_no_symptoms_min, time_infected_no_symptoms_max); + assign_uniform_distribution_array(params.get>(), + time_infected_symptoms_min, time_infected_symptoms_max); + assign_uniform_distribution_array(params.get>(), time_infected_severe_min, + time_infected_severe_max); + assign_uniform_distribution_array(params.get>(), + time_infected_critical_min, time_infected_critical_max); + + // --- Transmission Probabilities --- + // Adjusted for Omicron variant: doi.org/10.7554/eLife.78933 + const double variant_factor = 1.94; + const double transmission_prob_contact_min[num_age_groups] = {0.02 * variant_factor, 0.05 * variant_factor, + 0.05 * variant_factor, 0.05 * variant_factor, + 0.08 * variant_factor, 0.10 * variant_factor}; + const double transmission_prob_contact_max[num_age_groups] = {0.04 * variant_factor, 0.07 * variant_factor, + 0.07 * variant_factor, 0.07 * variant_factor, + 0.10 * variant_factor, 0.15 * variant_factor}; + + // Relative transmission from asymptomatic cases (fixed for simplicity, could use DOI: 10.1097/INF.0000000000003791) + const double rel_transmission_no_symptoms_min = 0.5; + const double rel_transmission_no_symptoms_max = 0.5; + + // Risk of infection from symptomatic cases (depends on incidence and testing capacity) + const double risk_infection_symptomatic_min = 0.0; + const double risk_infection_symptomatic_max = 0.2; + const double max_risk_infection_symptomatic_min = 0.4; + const double max_risk_infection_symptomatic_max = 0.5; + + assign_uniform_distribution_array(params.get>(), + transmission_prob_contact_min, transmission_prob_contact_max); + assign_uniform_distribution_array(params.get>(), + rel_transmission_no_symptoms_min, rel_transmission_no_symptoms_max); + assign_uniform_distribution_array(params.get>(), + risk_infection_symptomatic_min, risk_infection_symptomatic_max); + assign_uniform_distribution_array(params.get>(), + max_risk_infection_symptomatic_min, max_risk_infection_symptomatic_max); + + // --- Disease Progression Probabilities --- + // Recovery from asymptomatic infection: doi.org/10.1101/2022.05.05.22274697 + const double recovered_per_infected_no_symptoms_min[num_age_groups] = {0.2, 0.25, 0.2, 0.2, 0.175, 0.1}; + const double recovered_per_infected_no_symptoms_max[num_age_groups] = {0.4, 0.45, 0.35, 0.3, 0.25, 0.15}; + + // Severe cases from symptomatic infection (2021 data with factors): doi.org/10.1016/S0140-6736(22)00462-7 + const double severe_per_infected_symptoms_min[num_age_groups] = {1 * 0.006, 0.8 * 0.006, 0.4 * 0.015, + 0.3 * 0.049, 0.25 * 0.15, 0.35 * 0.2}; + const double severe_per_infected_symptoms_max[num_age_groups] = {1 * 0.009, 0.8 * 0.009, 0.4 * 0.023, + 0.3 * 0.074, 0.25 * 0.18, 0.35 * 0.25}; + + // Critical cases from severe (Delta-adjusted, factor 0.52): doi.org/10.1177/14034948221108548 + const double critical_factor = 0.52; + const double critical_per_severe_min[num_age_groups] = {0.05 * critical_factor, 0.05 * critical_factor, + 0.05 * critical_factor, 0.10 * critical_factor, + 0.25 * critical_factor, 0.35 * critical_factor}; + const double critical_per_severe_max[num_age_groups] = {0.10 * critical_factor, 0.10 * critical_factor, + 0.10 * critical_factor, 0.20 * critical_factor, + 0.35 * critical_factor, 0.45 * critical_factor}; + + // Deaths from critical cases (factor 0.39): doi.org/10.1136/bmjgh-2023-012328 + const double death_factor = 0.39; + const double deaths_per_critical_min[num_age_groups] = {0.00 * death_factor, 0.00 * death_factor, + 0.10 * death_factor, 0.10 * death_factor, + 0.30 * death_factor, 0.50 * death_factor}; + const double deaths_per_critical_max[num_age_groups] = {0.10 * death_factor, 0.10 * death_factor, + 0.18 * death_factor, 0.18 * death_factor, + 0.50 * death_factor, 0.70 * death_factor}; + + assign_uniform_distribution_array(params.get>(), + recovered_per_infected_no_symptoms_min, recovered_per_infected_no_symptoms_max); + assign_uniform_distribution_array(params.get>(), + severe_per_infected_symptoms_min, severe_per_infected_symptoms_max); + assign_uniform_distribution_array(params.get>(), critical_per_severe_min, + critical_per_severe_max); + assign_uniform_distribution_array(params.get>(), deaths_per_critical_min, + deaths_per_critical_max); + + // --- Immunity Parameters --- + // Exposure reduction (no reduction assumed here) + const double reduc_exposed_partial_immunity_min = 1.0; + const double reduc_exposed_partial_immunity_max = 1.0; + const double reduc_exposed_improved_immunity_min = 1.0; + const double reduc_exposed_improved_immunity_max = 1.0; + + // Symptom reduction: doi.org/10.1056/NEJMoa2119451 + const double reduc_infected_symptoms_partial_min = 0.746; + const double reduc_infected_symptoms_partial_max = 0.961; + const double reduc_infected_symptoms_improved_min = 0.295; + const double reduc_infected_symptoms_improved_max = 0.344; + + // Severe/critical/death reduction + // Partial immunity: doi.org/10.1056/NEJMoa2119451 (week 4 report) + const double reduc_severe_critical_dead_partial_min = 0.52; + const double reduc_severe_critical_dead_partial_max = 0.82; + // Improved immunity: doi.org/10.1136/bmj-2022-071502 + const double reduc_severe_critical_dead_improved_min = 0.1; + const double reduc_severe_critical_dead_improved_max = 0.19; + + // Time reduction for mild infections: doi.org/10.1101/2021.09.24.21263978 + const double reduc_time_infected_mild = 0.5; + + assign_uniform_distribution_array(params.get>(), + reduc_exposed_partial_immunity_min, reduc_exposed_partial_immunity_max); + assign_uniform_distribution_array(params.get>(), + reduc_exposed_improved_immunity_min, reduc_exposed_improved_immunity_max); + assign_uniform_distribution_array(params.get>(), + reduc_infected_symptoms_partial_min, reduc_infected_symptoms_partial_max); + assign_uniform_distribution_array(params.get>(), + reduc_infected_symptoms_improved_min, reduc_infected_symptoms_improved_max); + assign_uniform_distribution_array( params.get>(), - reducInfectedSevereCriticalDeadPartialImmunityMin, reducInfectedSevereCriticalDeadPartialImmunityMax); - array_assign_uniform_distribution( + reduc_severe_critical_dead_partial_min, reduc_severe_critical_dead_partial_max); + assign_uniform_distribution_array( params.get>(), - reducInfectedSevereCriticalDeadImprovedImmunityMin, reducInfectedSevereCriticalDeadImprovedImmunityMax); - array_assign_uniform_distribution(params.get>(), reducTimeInfectedMild, - reducTimeInfectedMild); + reduc_severe_critical_dead_improved_min, reduc_severe_critical_dead_improved_max); + assign_uniform_distribution_array(params.get>(), + reduc_time_infected_mild, reduc_time_infected_mild); - //sasonality + // --- Seasonality --- + // Seasonal variation const double seasonality_min = 0.1; const double seasonality_max = 0.3; - assign_uniform_distribution(params.get>(), seasonality_min, seasonality_max); - // Delta specific parameter + // --- Variant-Specific Parameters --- params.get() = mio::get_day_in_year(mio::Date(2022, 6, 6)); - const double ImmunityInterval1Min = 60; ///https://doi.org/10.1016/S1473-3099(22)00801-5 - const double ImmunityInterval1Max = 60; - - array_assign_uniform_distribution(params.get>(), - ImmunityInterval1Min, ImmunityInterval1Max); - - const double ImmunityInterval2Min = 60; // https://doi.org/10.1016/S1473-3099(22)00801-5 - const double ImmunityInterval2Max = 60; + // --- Waning Immunity Durations --- + // Temporary immunity periods: doi.org/10.1016/S1473-3099(22)00801-5 + const double immunity_interval_partial_min = 60; + const double immunity_interval_partial_max = 60; + const double immunity_interval_improved_min = 60; + const double immunity_interval_improved_max = 60; - array_assign_uniform_distribution(params.get>(), - ImmunityInterval2Min, ImmunityInterval2Max); + assign_uniform_distribution_array(params.get>(), + immunity_interval_partial_min, immunity_interval_partial_max); + assign_uniform_distribution_array(params.get>(), + immunity_interval_improved_min, immunity_interval_improved_max); - // https://doi.org/10.1016/S1473-3099(22)00801-5 + // Waning immunity duration: doi.org/10.1016/S1473-3099(22)00801-5 params.get>() = 365.0; params.get>() = 365.0; @@ -295,104 +297,152 @@ static const std::map contact_locations = {{Contac {ContactLocation::Other, "other"}}; /** - * Set contact matrices. - * Reads contact matrices from files in the data directory. - * @param data_dir data directory. - * @param params Object that the contact matrices will be added to. - * @returns any io errors that happen during reading of the files. + * Configures contact matrices for the model by reading data from files and applying scaling adjustments. + * @param[in] data_dir Directory containing contact data files. + * @param[in,out] params Parameters object to store the configured contact matrices. + * @param[in] avg_transport_time Average transport time (default: 0.0), used to scale contact frequencies. + * @param[in] share_staying_local Proportion of individuals staying local (default: 1.0), affects contact scaling. + * @return IOResult indicating success or an IO error if file reading fails. */ mio::IOResult set_contact_matrices(const fs::path& data_dir, mio::osecirts::Parameters& params, - ScalarType avg_tt = 0.0, ScalarType share_staying = 1.0) + double avg_transport_time = 0.0, double share_staying_local = 1.0) { - auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); - auto contact_matrix_transport = contact_transport_status.value(); - auto contact_matrices = mio::ContactMatrixGroup(contact_locations.size(), size_t(params.get_num_groups())); - for (auto&& contact_location : contact_locations) { - BOOST_OUTCOME_TRY(auto&& baseline, - mio::read_mobility_plain( - (data_dir / "contacts" / ("baseline_" + contact_location.second + ".txt")).string())); - - // the other contact matrix containts also the transport contact matrix. Therefore, we need to subtract it. - if (contact_location.second == "other") { - baseline = abs(baseline - contact_matrix_transport); + // Read transport contact matrix + BOOST_OUTCOME_TRY(auto&& transport_matrix, + mio::read_mobility_plain((data_dir / "contacts" / "contacts_transport.txt").string())); + + // Init contact matrices for all locations + constexpr size_t num_age_groups = 6; + mio::ContactMatrixGroup contact_matrices(contact_locations.size(), num_age_groups); + + for (const auto& [location_id, location_name] : contact_locations) { + // Read baseline contact matrix for this location + BOOST_OUTCOME_TRY( + auto&& baseline, + mio::read_mobility_plain((data_dir / "contacts" / ("baseline_" + location_name + ".txt")).string())); + + // Adjust "other" location by subtracting transport contacts + if (location_name == "other") { + baseline = (baseline - transport_matrix).cwiseAbs(); // Ensure non-negative values } - // Scale the number of contacts - auto scaled_baseline = (1 - share_staying) * baseline / (1 - avg_tt) + share_staying * baseline; - // Because we only model the acitvity from commuters, some age group should just have the unscaled contacts - Eigen::MatrixXd baseline_adjusted = Eigen::MatrixXd::Zero(scaled_baseline.rows(), scaled_baseline.cols()); - for (auto i = 0; i < 6; i++) { - for (auto j = 0; j < 6; j++) { + + // Scale contacts based on travel time and local staying proportion + auto scaled_baseline = + (1.0 - share_staying_local) * baseline / (1.0 - avg_transport_time) + share_staying_local * baseline; + + // Adjust contacts: only scale for commuting age groups (2-5), others remain unchanged + Eigen::MatrixXd adjusted_baseline = Eigen::MatrixXd::Zero(num_age_groups, num_age_groups); + for (size_t i = 0; i < num_age_groups; ++i) { + for (size_t j = 0; j < num_age_groups; ++j) { if ((i >= 2 && i <= 5) || (j >= 2 && j <= 5)) { - baseline_adjusted(i, j) = scaled_baseline(i, j); + adjusted_baseline(i, j) = scaled_baseline(i, j); } else { - baseline_adjusted(i, j) = baseline(i, j); + adjusted_baseline(i, j) = baseline(i, j); } } } - contact_matrices[size_t(contact_location.first)].get_baseline() = baseline_adjusted; - contact_matrices[size_t(contact_location.first)].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + + // Assign adjusted baseline and zero minimum to the contact matrix + contact_matrices[static_cast(location_id)].get_baseline() = adjusted_baseline; + contact_matrices[static_cast(location_id)].get_minimum() = + Eigen::MatrixXd::Zero(num_age_groups, num_age_groups); } + + // Store the configured contact matrices in the parameters object params.get>() = mio::UncertainContactMatrix(contact_matrices); return mio::success(); } +/** + * Configures a transport-specific contact matrix for the model by reading data from a file. + * @param[in] data_dir Directory containing the transport contact data file ("contacts/contacts_transport.txt"). + * @param[in,out] params Parameters object to store the configured transport contact matrix. + * @return IOResult indicating success or an IO error if file reading fails. + */ mio::IOResult set_contact_matrices_transport(const fs::path& data_dir, mio::osecirts::Parameters& params) { - auto contact_transport_status = mio::read_mobility_plain(data_dir.string() + "//contacts//contacts_transport.txt"); - auto contact_matrix_transport = contact_transport_status.value(); - auto contact_matrices = mio::ContactMatrixGroup(1, size_t(params.get_num_groups())); - // ScalarType const polymod_share_contacts_transport = 1 / 0.2770885028949545; + // Define the path to the transport contacts file + fs::path transport_file = data_dir / "contacts" / "contacts_transport.txt"; + + // number of age groups + const auto num_age_groups = static_cast(params.get_num_groups()); + + // Read the transport contact matrix from the file + BOOST_OUTCOME_TRY(auto&& transport_matrix, mio::read_mobility_plain(transport_file.string())); - contact_matrices[0].get_baseline() = contact_matrix_transport; - contact_matrices[0].get_minimum() = Eigen::MatrixXd::Zero(6, 6); + // Initialize contact matrices with a single group for transport + auto contact_matrices = mio::ContactMatrixGroup(1, num_age_groups); + // Assign the transport matrix as the baseline and set a zero minimum + contact_matrices[0].get_baseline() = transport_matrix; + contact_matrices[0].get_minimum() = Eigen::MatrixXd::Zero(num_age_groups, num_age_groups); + + // Store the configured contact matrix in the parameters object params.get>() = mio::UncertainContactMatrix(contact_matrices); return mio::success(); } +/** + * Scales contact matrices for all nodes in a graph based on commuting data and travel time. + * Computes average travel time and commuter share, then applies these to adjust local contact patterns. + * @param[in,out] params_graph Graph containing model parameters for each node, updated with scaled contact matrices. + * @param[in] data_dir Directory containing baseline contact data files. + * @param[in] mobility_data_dir Path to the file with commuter mobility data. + * @param[in] commuting_weights Weights for each age group to compute total population (size must match age groups). + * @return IOResult indicating success or an IO error if file reading or contact matrix setup fails. + */ mio::IOResult scale_contacts_local(mio::ExtendedGraph>& params_graph, - const fs::path& data_dir, const std::string mobility_data_dir, - const std::vector commuting_weights) + const fs::path& data_dir, const std::string& mobility_data_dir, + const std::vector& commuting_weights) { + // Read commuter mobility data BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); - // average travel time - const ScalarType avg_traveltime = std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, - [](double sum, const auto& e) { - return sum + e.property.travel_time; - }) / - params_graph.edges().size(); - - // average share of commuters for all counties relative to the total population - const ScalarType total_population = std::accumulate( - params_graph.nodes().begin(), params_graph.nodes().end(), 0.0, [commuting_weights](double sum, const auto& n) { - return sum + n.property.base_sim.populations.get_group_total(mio::AgeGroup(0)) * commuting_weights[0] + - n.property.base_sim.populations.get_group_total(mio::AgeGroup(1)) * commuting_weights[1] + - n.property.base_sim.populations.get_group_total(mio::AgeGroup(2)) * commuting_weights[2] + - n.property.base_sim.populations.get_group_total(mio::AgeGroup(3)) * commuting_weights[3] + - n.property.base_sim.populations.get_group_total(mio::AgeGroup(4)) * commuting_weights[4] + - n.property.base_sim.populations.get_group_total(mio::AgeGroup(5)) * commuting_weights[5]; - }); - const ScalarType num_commuters = std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, - [mobility_data_commuter](double sum, const auto& e) { - auto start_node = e.start_node_idx; - auto end_node = e.end_node_idx; - return sum + mobility_data_commuter(start_node, end_node); - }); - const ScalarType avg_commuter_share = num_commuters / total_population; + // Calculate average travel time across all edges + const double avg_travel_time = std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, + [](double sum, const auto& edge) { + return sum + edge.property.travel_time; + }) / + static_cast(params_graph.edges().size()); + + // Calculate total population weighted by age group commuting factors + const double total_population = + std::accumulate(params_graph.nodes().begin(), params_graph.nodes().end(), 0.0, + [&commuting_weights](double sum, const auto& node) { + const auto& populations = node.property.base_sim.populations; + return sum + populations.get_group_total(mio::AgeGroup(0)) * commuting_weights[0] + + populations.get_group_total(mio::AgeGroup(1)) * commuting_weights[1] + + populations.get_group_total(mio::AgeGroup(2)) * commuting_weights[2] + + populations.get_group_total(mio::AgeGroup(3)) * commuting_weights[3] + + populations.get_group_total(mio::AgeGroup(4)) * commuting_weights[4] + + populations.get_group_total(mio::AgeGroup(5)) * commuting_weights[5]; + }); + + // Calculate total number of commuters from mobility data + const double total_commuters = + std::accumulate(params_graph.edges().begin(), params_graph.edges().end(), 0.0, + [&mobility_data_commuter](double sum, const auto& edge) { + return sum + mobility_data_commuter(edge.start_node_idx, edge.end_node_idx); + }); + + // Compute average commuter share relative to total population + const double avg_commuter_share = total_commuters / total_population; + + // Log results on the root MPI process if (mio::mpi::is_root()) { - std::cout << "avg_commuter_share: " << avg_commuter_share << std::endl; - std::cout << "avg_traveltime: " << avg_traveltime << std::endl; + std::cout << "Average commuter share: " << avg_commuter_share << "\n"; + std::cout << "Average travel time: " << avg_travel_time << "\n"; } - // scale all contact matrices + // Scale contact matrices for each node using computed averages for (auto& node : params_graph.nodes()) { BOOST_OUTCOME_TRY( - set_contact_matrices(data_dir, node.property.base_sim.parameters, avg_traveltime, avg_commuter_share)); + set_contact_matrices(data_dir, node.property.base_sim.parameters, avg_travel_time, avg_commuter_share)); } + return mio::success(); } @@ -541,7 +591,7 @@ set_nodes(const mio::osecirts::Parameters& params, mio::Date start_date, mio transmissionProbabilityOnContactMin[i] = transmissionProbabilityOnContactMin[i] * factor_mask[i]; transmissionProbabilityOnContactMax[i] = transmissionProbabilityOnContactMax[i] * factor_mask[i]; } - array_assign_uniform_distribution( + assign_uniform_distribution_array( mobility_model.parameters.template get>(), transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); } From dd659525095d219482ae0096912b3e7d7a31743f Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 13 Mar 2025 15:29:53 +0100 Subject: [PATCH 22/26] doc for simulation --- .../2022_omicron_late_phase_mobility.cpp | 615 +++++++++--------- 1 file changed, 301 insertions(+), 314 deletions(-) diff --git a/cpp/simulations/2022_omicron_late_phase_mobility.cpp b/cpp/simulations/2022_omicron_late_phase_mobility.cpp index cc25c272b3..bfb348fb73 100644 --- a/cpp/simulations/2022_omicron_late_phase_mobility.cpp +++ b/cpp/simulations/2022_omicron_late_phase_mobility.cpp @@ -446,67 +446,29 @@ mio::IOResult scale_contacts_local(mio::ExtendedGraph, mio::MobilityParameters>& graph, - const int id_cologne) -{ - std::vector> immunity = {{0.04, 0.61, 0.35}, {0.04, 0.61, 0.35}, {0.075, 0.62, 0.305}, - {0.08, 0.62, 0.3}, {0.035, 0.58, 0.385}, {0.01, 0.41, 0.58}}; - - for (auto& node : graph.nodes()) { - for (auto age = mio::AgeGroup(0); age < mio::AgeGroup(6); age++) { - auto pop_age = 0.0; - for (auto inf_state = mio::Index(0); - inf_state < mio::osecirts::InfectionState::Count; ++inf_state) { - pop_age += node.property.populations[{age, inf_state}]; - node.property.populations[{age, inf_state}] = 0.0; - } - size_t immunity_index = static_cast(age); - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] = - pop_age * immunity[immunity_index][0]; - node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] = - pop_age * immunity[immunity_index][1]; - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] = - pop_age * immunity[immunity_index][2]; - } - if (node.id == id_cologne) { - // infect p% of population - ScalarType p = 0.05; - for (auto age = mio::AgeGroup(0); age < graph.nodes()[0].property.parameters.get_num_groups(); age++) { - node.property.populations[{mio::AgeGroup(age), mio::osecirts::InfectionState::InfectedSymptomsNaive}] = - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] * p; - node.property.populations[{age, mio::osecirts::InfectionState::InfectedSymptomsPartialImmunity}] = - node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] * p; - node.property.populations[{mio::AgeGroup(age), - mio::osecirts::InfectionState::InfectedSymptomsImprovedImmunity}] = - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] * p; - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleNaive}] *= (1 - p); - node.property.populations[{age, mio::osecirts::InfectionState::SusceptiblePartialImmunity}] *= (1 - p); - node.property.populations[{age, mio::osecirts::InfectionState::SusceptibleImprovedImmunity}] *= (1 - p); - } - } - } -} - /** - * @brief Sets the graph nodes for counties or districts. - * Reads the node ids which could refer to districts or counties and the epidemiological - * data from json files and creates one node for each id. Every node contains a model. - * @param[in] params Model Parameters that are used for every node. - * @param[in] start_date Start date for which the data should be read. - * @param[in] end_data End date for which the data should be read. - * @param[in] data_dir Directory that contains the data files. - * @param[in] population_data_path Path to json file containing the population data. - * @param[in] stay_times_data_path Path to txt file containing the stay times for the considered local entities. - * @param[in] is_node_for_county Specifies whether the node ids should be county ids (true) or district ids (false). - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] read_func Function that reads input data for german counties and sets Model compartments. - * @param[in] node_func Function that returns the county ids. - * @param[in] scaling_factor_inf Factor of confirmed cases to account for undetected cases in each county. - * @param[in] scaling_factor_icu Factor of ICU cases to account for underreporting. - * @param[in] tnt_capacity_factor Factor for test and trace capacity. - * @param[in] num_days Number of days to be simulated; required to load data for vaccinations during the simulation. - * @param[in] export_time_series If true, reads data for each day of simulation and writes it in the same directory as the input files. + * @brief Configures graph nodes for counties with epidemiological data. + * Reads node IDs and population data from files and initializes models for each node. + * @param[in] params Model parameters applied to all nodes. + * @param[in] start_date Start date for reading epidemiological data. + * @param[in] end_date End date for reading epidemiological data. + * @param[in] data_dir Directory containing data files (e.g., contact matrices). + * @param[in] population_data_path Path to JSON file with population data. + * @param[in] stay_times_data_path Path to TXT file with stay times for local entities. + * @param[in] is_node_for_county True for county IDs, false for district IDs. + * @param[in,out] params_graph Graph to populate with nodes. + * @param[in] read_func Function to read input data and set model compartments. + * @param[in] node_func Function to get node IDs from population data. + * @param[in] scaling_factor_inf Factors for confirmed cases to adjust for undetected infections. + * @param[in] scaling_factor_icu Factor for ICU cases to adjust for underreporting. + * @param[in] tnt_capacity_factor Factor for test-and-trace capacity relative to population. + * @param[in] immunity_population Immunity distribution per age group across. + * @param[in] num_days Number of simulation days (default: 0), used for vaccination data. + * @param[in] export_time_series If true, exports daily time series data to the input directory. + * @param[in] rki_age_groups If true, uses RKI age group definitions. + * @param[in] masks If true, applies mask-related transmission reductions in mobility nodes. + * @param[in] ffp2 If true, uses FFP2 mask factors; otherwise, uses surgical mask factors. + * @return IOResult indicating success or an IO error if file reading fails. */ template mio::IOResult @@ -514,44 +476,49 @@ set_nodes(const mio::osecirts::Parameters& params, mio::Date start_date, mio const fs::path& data_dir, const std::string& population_data_path, const std::string& stay_times_data_path, bool is_node_for_county, mio::ExtendedGraph>& params_graph, ReadFunction&& read_func, NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, FP scaling_factor_icu, - FP tnt_capacity_factor, const std::vector> immunity_population, int num_days = 0, + FP tnt_capacity_factor, const std::vector>& immunity_population, int num_days = 0, bool export_time_series = false, bool rki_age_groups = true, bool masks = false, bool ffp2 = false) { - + // Read stay times for mobility BOOST_OUTCOME_TRY(auto&& duration_stay, mio::read_duration_stay(stay_times_data_path)); + // Get node IDs (counties or districts) BOOST_OUTCOME_TRY(auto&& node_ids, node_func(population_data_path, is_node_for_county, rki_age_groups)); + + // Initialize models for each node std::vector> nodes(node_ids.size(), - mio::osecirts::Model(int(size_t(params.get_num_groups())))); + mio::osecirts::Model(static_cast(params.get_num_groups()))); for (auto& node : nodes) { - node.parameters = params; + node.parameters = params; // Assign base parameters to each node } + // Populate nodes with epidemiological data BOOST_OUTCOME_TRY(read_func(nodes, start_date, node_ids, scaling_factor_inf, scaling_factor_icu, data_dir.string(), num_days, immunity_population, export_time_series)); + // Configure each node for (size_t node_idx = 0; node_idx < nodes.size(); ++node_idx) { - - auto tnt_capacity = nodes[node_idx].populations.get_total() * tnt_capacity_factor; - - //local parameters - auto& tnt_value = nodes[node_idx].parameters.template get>(); - tnt_value = mio::UncertainValue(0.5 * (1.2 * tnt_capacity + 0.8 * tnt_capacity)); - tnt_value.set_distribution(mio::ParameterDistributionUniform(0.8 * tnt_capacity, 1.2 * tnt_capacity)); - - //holiday periods - auto id = int(mio::regions::CountyId(node_ids[node_idx])); - auto holiday_periods = mio::regions::get_holidays(mio::regions::get_state_id(id), start_date, end_date); - auto& contacts = nodes[node_idx].parameters.template get>(); - contacts.get_school_holidays() = - std::vector>(holiday_periods.size()); - std::transform( - holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), [=](auto& period) { - return std::make_pair(mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), - mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); - }); - - //uncertainty in populations + auto& node = nodes[node_idx]; + + // Set test-and-trace capacity with uncertainty + const FP tnt_capacity = node.populations.get_total() * tnt_capacity_factor; + auto& tnt_value = node.parameters.template get>(); + assign_uniform_distribution(tnt_value, 0.8 * tnt_capacity, 1.2 * tnt_capacity); + + // Configure school holidays based on node's state + const int county_id = static_cast(mio::regions::CountyId(node_ids[node_idx])); + const auto holiday_periods = + mio::regions::get_holidays(mio::regions::get_state_id(county_id), start_date, end_date); + auto& contacts = node.parameters.template get>(); + contacts.get_school_holidays().resize(holiday_periods.size()); + std::transform(holiday_periods.begin(), holiday_periods.end(), contacts.get_school_holidays().begin(), + [start_date](const auto& period) { + return std::make_pair( + mio::SimulationTime(mio::get_offset_in_days(period.first, start_date)), + mio::SimulationTime(mio::get_offset_in_days(period.second, start_date))); + }); + + // Add uncertainty to population compartments for (auto i = mio::AgeGroup(0); i < params.get_num_groups(); i++) { for (auto j = mio::Index::Compartments>(0); j < mio::osecirts::Model::Compartments::Count; ++j) { @@ -563,111 +530,116 @@ set_nodes(const mio::osecirts::Parameters& params, mio::Date start_date, mio } } - // Add mobility node - auto mobility_model = nodes[node_idx]; + // Create mobility node with zero population + auto mobility_model = node; mobility_model.populations.set_total(0); - // reduce transmission on contact due to mask obligation in mobility node + // Adjust transmission probability in mobility node if masks are enabled if (masks) { - - const double fact_surgical_mask = 0.1; - const double fact_ffp2 = 0.001; - - // first age group not able to (properly) wear masks, second age group only partially - double factor_mask[] = {1, fact_surgical_mask, fact_ffp2, fact_ffp2, fact_ffp2, fact_ffp2}; - if (!ffp2) { - for (size_t j = 2; j < 6; j++) { - factor_mask[j] = factor_mask[j] * fact_surgical_mask / fact_ffp2; - } - } - - double fac_variant = 1.94; //https://doi.org/10.7554/eLife.78933 - double transmissionProbabilityOnContactMin[] = {0.02 * fac_variant, 0.05 * fac_variant, 0.05 * fac_variant, - 0.05 * fac_variant, 0.08 * fac_variant, 0.1 * fac_variant}; - - double transmissionProbabilityOnContactMax[] = {0.04 * fac_variant, 0.07 * fac_variant, 0.07 * fac_variant, - 0.07 * fac_variant, 0.10 * fac_variant, 0.15 * fac_variant}; - for (int i = 0; i < 6; i++) { - transmissionProbabilityOnContactMin[i] = transmissionProbabilityOnContactMin[i] * factor_mask[i]; - transmissionProbabilityOnContactMax[i] = transmissionProbabilityOnContactMax[i] * factor_mask[i]; + constexpr double surgical_mask_factor = 0.1; // Reduction factor for surgical masks + constexpr double ffp2_mask_factor = 0.001; // Reduction factor for FFP2 masks + double mask_factors[] = {1.0, + surgical_mask_factor, + ffp2 ? ffp2_mask_factor : surgical_mask_factor, + ffp2 ? ffp2_mask_factor : surgical_mask_factor, + ffp2 ? ffp2_mask_factor : surgical_mask_factor, + ffp2 ? ffp2_mask_factor : surgical_mask_factor}; + + constexpr double variant_factor = 1.94; // Omicron adjustment: doi.org/10.7554/eLife.78933 + double trans_prob_min[] = {0.02 * variant_factor, 0.05 * variant_factor, 0.05 * variant_factor, + 0.05 * variant_factor, 0.08 * variant_factor, 0.10 * variant_factor}; + double trans_prob_max[] = {0.04 * variant_factor, 0.07 * variant_factor, 0.07 * variant_factor, + 0.07 * variant_factor, 0.10 * variant_factor, 0.15 * variant_factor}; + + for (int i = 0; i < 6; ++i) { + trans_prob_min[i] *= mask_factors[i]; + trans_prob_max[i] *= mask_factors[i]; } assign_uniform_distribution_array( mobility_model.parameters.template get>(), - transmissionProbabilityOnContactMin, transmissionProbabilityOnContactMax); + trans_prob_min, trans_prob_max); } - // no vaccination in mobility node - for (int t_idx = 0; t_idx < num_days; ++t_idx) { - auto t = mio::SimulationDay((size_t)t_idx); - for (auto j = mio::AgeGroup(0); j < params.get_num_groups(); j++) { - mobility_model.parameters.template get>()[{j, t}] = 0; - mobility_model.parameters.template get>()[{j, t}] = 0; - mobility_model.parameters.template get>()[{j, t}] = 0; + // Disable vaccinations in mobility node + for (int day = 0; day < num_days; ++day) { + auto t = mio::SimulationDay(static_cast(day)); + for (auto age = mio::AgeGroup(0); age < params.get_num_groups(); ++age) { + mobility_model.parameters.template get>()[{age, t}] = 0; + mobility_model.parameters.template get>()[{age, t}] = 0; + mobility_model.parameters.template get>()[{age, t}] = 0; } } - params_graph.add_node(node_ids[node_idx], nodes[node_idx], mobility_model, - duration_stay((Eigen::Index)node_idx)); + // Add node to graph with its mobility counterpart and stay duration + params_graph.add_node(node_ids[node_idx], node, mobility_model, + duration_stay(static_cast(node_idx))); } + return mio::success(); } /** - * @brief Sets the graph edges. - * Reads the commuting matrices, travel times and paths from data and creates one edge for each pair of nodes. - * @param[in] travel_times_path Path to txt file containing the travel times between counties. - * @param[in] mobility_data_path Path to txt file containing the commuting matrices. - * @param[in] travelpath_path Path to txt file containing the paths between counties. - * @param[in, out] params_graph Graph whose nodes are set by the function. - * @param[in] migrating_compartments Compartments that commute. - * @param[in] contact_locations_size Number of contact locations. - * @param[in] commuting_weights Vector with a commuting weight for every AgeGroup. + * @brief Configures graph edges for commuting between counties or districts. + * Reads commuting matrices, travel times, and paths from files, then creates edges between node pairs with sufficient mobility. + * @param[in] travel_times_dir Path to TXT file containing travel times between counties. + * @param[in] mobility_data_dir Path to TXT file containing commuting matrices. + * @param[in] travel_path_dir Path to TXT file containing paths between counties. + * @param[in,out] params_graph Graph to add with edges. + * @param[in] migrating_compartments Infection states that participate in commuting. + * @param[in] contact_locations_size Number of contact locations (e.g., home, work) for mobility coefficients. + * @param[in] commuting_weights Weights for each age groups commuting contribution (default: empty, filled with 1.0). + * @param[in] threshold_edges Minimum commuter coefficient to create an edge (default: 4e-5). + * @return IOResult indicating success or an IO error if file reading fails. */ -mio::IOResult -set_edges(const std::string& travel_times_dir, const std::string mobility_data_dir, const std::string& travel_path_dir, - mio::ExtendedGraph>& params_graph, - std::initializer_list& migrating_compartments, size_t contact_locations_size, - std::vector commuting_weights = std::vector{}, ScalarType theshold_edges = 4e-5) +mio::IOResult set_edges(const std::string& travel_times_dir, const std::string& mobility_data_dir, + const std::string& travel_path_dir, + mio::ExtendedGraph>& params_graph, + const std::vector& migrating_compartments, + size_t contact_locations_size, std::vector commuting_weights = {}, + const double threshold_edges = 4e-5) { + // Read mobility, travel times, and path data BOOST_OUTCOME_TRY(auto&& mobility_data_commuter, mio::read_mobility_plain(mobility_data_dir)); BOOST_OUTCOME_TRY(auto&& travel_times, mio::read_mobility_plain(travel_times_dir)); BOOST_OUTCOME_TRY(auto&& path_mobility, mio::read_path_mobility(travel_path_dir)); + // Iterate over all pairs of nodes to set edges for (size_t county_idx_i = 0; county_idx_i < params_graph.nodes().size(); ++county_idx_i) { for (size_t county_idx_j = 0; county_idx_j < params_graph.nodes().size(); ++county_idx_j) { auto& populations = params_graph.nodes()[county_idx_i].property.base_sim.populations; + const size_t num_age_groups = + static_cast(params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups()); - // mobility coefficients have the same number of components as the contact matrices. - // so that the same NPIs/dampings can be used for both (e.g. more home office => fewer commuters) + // Initialize mobility coefficients aligned with contact locations auto mobility_coeffs = mio::MobilityCoefficientGroup(contact_locations_size, populations.numel()); - auto num_age_groups = - (size_t)params_graph.nodes()[county_idx_i].property.base_sim.parameters.get_num_groups(); - commuting_weights = - (commuting_weights.size() == 0 ? std::vector(num_age_groups, 1.0) : commuting_weights); - - //commuters - auto working_population = 0.0; - auto min_commuter_age = mio::AgeGroup(2); - auto max_commuter_age = mio::AgeGroup(4); //this group is partially retired, only partially commutes + + // Default commuting weights to 1.0 for all age groups if not provided + if (commuting_weights.empty()) { + commuting_weights.resize(num_age_groups, 1.0); + } + + // Calculate working population weighted by commuting factors + double working_population = 0.0; + const auto min_commuter_age = mio::AgeGroup(2); // Youngest commuting age + const auto max_commuter_age = mio::AgeGroup(4); // Partially retired group for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { - working_population += populations.get_group_total(age) * commuting_weights[size_t(age)]; + working_population += populations.get_group_total(age) * commuting_weights[static_cast(age)]; } - auto commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; + + // Compute commuter coefficient from i to j + const double commuter_coeff_ij = mobility_data_commuter(county_idx_i, county_idx_j) / working_population; + + // Assign mobility coefficients for commuting compartments at work location for (auto age = min_commuter_age; age <= max_commuter_age; ++age) { for (auto compartment : migrating_compartments) { - auto coeff_index = populations.get_flat_index({age, compartment}); - mobility_coeffs[size_t(ContactLocation::Work)].get_baseline()[coeff_index] = - commuter_coeff_ij * commuting_weights[size_t(age)]; + const size_t coeff_index = populations.get_flat_index({age, compartment}); + mobility_coeffs[static_cast(ContactLocation::Work)].get_baseline()[coeff_index] = + commuter_coeff_ij * commuting_weights[static_cast(age)]; } } - auto path = path_mobility[county_idx_i][county_idx_j]; - if (static_cast(path[0]) != county_idx_i || - static_cast(path[path.size() - 1]) != county_idx_j) - std::cout << "Wrong Path for edge " << county_idx_i << " " << county_idx_j << "\n"; - - //only add edges with mobility above thresholds for performance - if (commuter_coeff_ij > theshold_edges) { + // Add edge if commuter coefficient exceeds threshold + if (commuter_coeff_ij > threshold_edges) { params_graph.add_edge(county_idx_i, county_idx_j, std::move(mobility_coeffs), travel_times(county_idx_i, county_idx_j), path_mobility[county_idx_i][county_idx_j]); @@ -679,203 +651,203 @@ set_edges(const std::string& travel_times_dir, const std::string mobility_data_d } /** - * Create the input graph for the parameter study. - * Reads files from the data directory. - * @param start_date start date of the simulation. - * @param end_date end date of the simulation. - * @param data_dir data directory. - * @returns created graph or any io errors that happen during reading of the files. + * @brief Creates an input graph for a parameter study by reading data from files. + * @param[in] start_date Start date of the simulation. + * @param[in] end_date End date of the simulation. + * @param[in] num_days Number of days to simulate. + * @param[in] data_dir Directory containing input data files. + * @param[in] masks If true, applies mask-related transmission reductions. + * @param[in] ffp2 If true, uses FFP2 mask factors; otherwise, uses surgical masks (requires masks=true). + * @param[in] edges If true, configures graph edges for mobility. + * @return IOResult containing the created graph or an IO error if file reading fails. */ -mio::IOResult>> get_graph(const mio::Date start_date, - const mio::Date end_date, const int num_days, - const std::string& data_dir, bool masks, - bool ffp2, bool szenario_cologne, bool edges) +mio::IOResult>> +get_graph(mio::Date start_date, const mio::Date end_date, const int num_days, const std::string& data_dir, + const bool masks, const bool ffp2, const std::vector> immunity_population, + const bool edges) { - mio::unused(szenario_cologne); - std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); - std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); - - const std::vector> immunity_population = {{0.04, 0.04, 0.075, 0.08, 0.035, 0.01}, - {0.61, 0.61, 0.62, 0.62, 0.58, 0.41}, - {0.35, 0.35, 0.305, 0.3, 0.385, 0.58}}; - - // global parameters - const int num_age_groups = 6; - mio::osecirts::Parameters params(num_age_groups); - params.get() = mio::get_day_in_year(start_date); - auto params_status = set_covid_parameters(params); - auto contacts_status = set_contact_matrices(data_dir, params); + // file paths + const std::string travel_times_dir = mio::path_join(data_dir, "mobility", "travel_times_pathes.txt"); + const std::string durations_dir = mio::path_join(data_dir, "mobility", "activity_duration_work.txt"); + const std::string mobility_data = mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"); + const std::string travel_paths = mio::path_join(data_dir, "mobility", "wegketten_ohne_komma.txt"); + const std::string population_file = mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"); + + // Initialize global parameters + constexpr int num_age_groups = 6; + mio::osecirts::Parameters params(num_age_groups); params.get() = mio::get_day_in_year(start_date); - // create graph + // Configure parameters and contact matrices + BOOST_OUTCOME_TRY(set_covid_parameters(params)); + BOOST_OUTCOME_TRY(set_contact_matrices(data_dir, params)); + + // Create graph mio::ExtendedGraph> params_graph; + const std::vector scaling_factor_infected(num_age_groups, 1.7); + constexpr double scaling_factor_icu = 1.0; + constexpr double tnt_capacity_factor = 1.43 / 100000.0; - // set nodes - auto scaling_factor_infected = std::vector(size_t(params.get_num_groups()), 1.7); - auto scaling_factor_icu = 1.0; - auto tnt_capacity_factor = 1.43 / 100000.; - const auto& read_function_nodes = mio::osecirts::read_input_data_county>; - const auto& node_id_function = mio::get_node_ids; - - auto set_nodes_status = set_nodes( - params, start_date, end_date, data_dir, - mio::path_join(data_dir, "pydata", "Germany", "county_current_population.json"), durations_dir, true, - params_graph, read_function_nodes, node_id_function, scaling_factor_infected, scaling_factor_icu, - tnt_capacity_factor, immunity_population, num_days, false, true, ffp2, masks); - - if (!set_nodes_status) { - return set_nodes_status.error(); - } + BOOST_OUTCOME_TRY(set_nodes(params, start_date, end_date, data_dir, population_file, durations_dir, true, + params_graph, mio::osecirts::read_input_data_county>, + mio::get_node_ids, scaling_factor_infected, scaling_factor_icu, tnt_capacity_factor, + immunity_population, num_days, false, true, masks, ffp2)); - // iterate over all nodes and set the contact_matrix for the mobility models + // Set transport contact matrices for mobility models in all nodes for (auto& node : params_graph.nodes()) { BOOST_OUTCOME_TRY(set_contact_matrices_transport(data_dir, node.property.mobility_sim.parameters)); } - // set edges + // Add edges if enabled if (edges) { - auto migrating_compartments = {mio::osecirts::InfectionState::SusceptibleNaive, - mio::osecirts::InfectionState::ExposedNaive, - mio::osecirts::InfectionState::InfectedNoSymptomsNaive, - mio::osecirts::InfectionState::InfectedSymptomsNaive, - mio::osecirts::InfectionState::SusceptibleImprovedImmunity, - mio::osecirts::InfectionState::SusceptiblePartialImmunity, - mio::osecirts::InfectionState::ExposedPartialImmunity, - mio::osecirts::InfectionState::InfectedNoSymptomsPartialImmunity, - mio::osecirts::InfectionState::InfectedSymptomsPartialImmunity, - mio::osecirts::InfectionState::ExposedImprovedImmunity, - mio::osecirts::InfectionState::InfectedNoSymptomsImprovedImmunity, - mio::osecirts::InfectionState::InfectedSymptomsImprovedImmunity}; - auto set_edges_status = set_edges( - travel_times_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), - mio::path_join(data_dir, "mobility", "wegketten_ohne_komma.txt"), params_graph, migrating_compartments, - contact_locations.size(), std::vector{0., 0., 1.0, 1.0, 0.33, 0., 0.}); - - if (!set_edges_status) { - return set_edges_status.error(); - } - - // if we have edges/mobility, we also need to scale the local contacts - BOOST_OUTCOME_TRY(scale_contacts_local( - params_graph, data_dir, mio::path_join(data_dir, "mobility", "commuter_migration_with_locals.txt"), - std::vector{0., 0., 1.0, 1.0, 0.33, 0., 0.})); + const auto migrating_compartments = {mio::osecirts::InfectionState::SusceptibleNaive, + mio::osecirts::InfectionState::ExposedNaive, + mio::osecirts::InfectionState::InfectedNoSymptomsNaive, + mio::osecirts::InfectionState::InfectedSymptomsNaive, + mio::osecirts::InfectionState::SusceptibleImprovedImmunity, + mio::osecirts::InfectionState::SusceptiblePartialImmunity, + mio::osecirts::InfectionState::ExposedPartialImmunity, + mio::osecirts::InfectionState::InfectedNoSymptomsPartialImmunity, + mio::osecirts::InfectionState::InfectedSymptomsPartialImmunity, + mio::osecirts::InfectionState::ExposedImprovedImmunity, + mio::osecirts::InfectionState::InfectedNoSymptomsImprovedImmunity, + mio::osecirts::InfectionState::InfectedSymptomsImprovedImmunity}; + + const std::vector commuting_weights = {0.0, 0.0, 1.0, 1.0, 0.33, 0.0}; + BOOST_OUTCOME_TRY(set_edges(travel_times_dir, mobility_data, travel_paths, params_graph, migrating_compartments, + contact_locations.size(), commuting_weights)); + BOOST_OUTCOME_TRY(scale_contacts_local(params_graph, data_dir, mobility_data, commuting_weights)); } return params_graph; } -mio::IOResult run(const std::string data_dir, std::string res_dir, const int num_runs, const int num_days) + +/** + * @brief Runs a parameter study simulation and saves results. + * Configures a graph, runs multiple simulations, and exports time series and flow data. + * @param[in] data_dir Directory containing input data files. + * @param[in] res_dir Directory to save simulation results. + * @param[in] num_runs Number of simulation runs for the parameter study. + * @param[in] num_days Number of days to simulate. + * @param[in] masks Whether masks are enabled. + * @param[in] ffp2 Whether FFP2 is enabled. + * @param[in] edges Whether edges should be used. + * @return IOResult indicating success or an error during execution. + */ +mio::IOResult run(const std::string& data_dir, std::string res_dir, int num_runs, int num_days, bool masks, + bool ffp2, bool edges) { + // Uncomment to suppress logs // mio::set_log_level(mio::LogLevel::critical); - auto start_date = mio::Date(2022, 8, 1); - auto end_date = mio::Date(2022, 11, 1); - constexpr bool masks = true; - constexpr bool ffp2 = true; - const bool edges = true; - bool szenario_cologne = false; - - // wenn masks false und ffp2 true, dann error ausgeben - if constexpr (!masks && ffp2) { - mio::log_error("ffp2 only possible with masks"); + const mio::Date start_date(2022, 8, 1); + const mio::Date end_date(2022, 11, 1); + + if (!masks && ffp2) { + return mio::failure(mio::StatusCode::InvalidValue, "FFP2 only possible with masks enabled"); } - // auto params_graph = get_graph(num_days, masks); - BOOST_OUTCOME_TRY(auto&& created, - get_graph(start_date, end_date, num_days, data_dir, masks, ffp2, szenario_cologne, edges)); - auto params_graph = created; + const auto immunity_population = + std::vector>{{0.04, 0.61, 0.35}, {0.04, 0.61, 0.35}, {0.075, 0.62, 0.305}, + {0.08, 0.62, 0.3}, {0.035, 0.58, 0.385}, {0.01, 0.41, 0.58}}; - res_dir += "/masks_" + std::to_string(masks) + std::string(ffp2 ? "_ffp2" : "") + - std::string(szenario_cologne ? "_cologne" : "") + std::string(!edges ? "_no_edges" : ""); + BOOST_OUTCOME_TRY(auto params_graph, + get_graph(start_date, end_date, num_days, data_dir, masks, ffp2, immunity_population, edges)); - if (mio::mpi::is_root()) - std::cout << "res_dir = " << res_dir << "\n"; + // Construct result directory name + res_dir += "/masks_" + std::to_string(masks) + (ffp2 ? "_ffp2" : "") + (!edges ? "_no_edges" : ""); + if (mio::mpi::is_root()) { + std::cout << "Result directory: " << res_dir << "\n"; + } - // check if res_dir dir exist, otherwise create + // Ensure result directory exists if (!fs::exists(res_dir)) { fs::create_directories(res_dir); } - std::vector county_ids(params_graph.nodes().size()); - std::transform(params_graph.nodes().begin(), params_graph.nodes().end(), county_ids.begin(), [](auto& n) { - return n.id; - }); + // Extract county IDs from graph nodes + std::vector county_ids; + county_ids.reserve(params_graph.nodes().size()); + std::transform(params_graph.nodes().begin(), params_graph.nodes().end(), std::back_inserter(county_ids), + [](const auto& node) { + return node.id; + }); - // mio::ExtendedGraph>> - // parameter study + // Run parameter study auto parameter_study = mio::ParameterStudy< mio::osecirts::Simulation>>, mio::ExtendedGraph>, mio::ExtendedGraph< mio::osecirts::Simulation>>>>( params_graph, 0.0, num_days, 0.01, num_runs); + if (mio::mpi::is_root()) { - printf("Seeds: "); - for (auto s : parameter_study.get_rng().get_seeds()) { - printf("%u, ", s); + std::cout << "Seeds: "; + for (auto seed : parameter_study.get_rng().get_seeds()) { + std::cout << seed << ", "; } - printf("\n"); + std::cout << "\n"; } - auto save_single_run_result = mio::IOResult(mio::success()); - auto ensemble = parameter_study.run( - [&](auto&& graph) { + + auto ensemble = parameter_study.run( + [](auto&& graph) { return draw_sample(graph); }, - [&](auto results_graph, auto&& run_idx) { - std::vector> interpolated_result; - interpolated_result.reserve(results_graph.nodes().size()); - std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), - std::back_inserter(interpolated_result), [](auto& n) { - return interpolate_simulation_result(n.property.base_sim.get_result()); + [&](auto&& results_graph, size_t run_idx) { + // get base simulation results + std::vector> base_results; + base_results.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(base_results), + [](auto& node) { + return mio::interpolate_simulation_result(node.property.base_sim.get_result()); }); - auto params = std::vector>(); - params.reserve(results_graph.nodes().size()); - std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), - [](auto&& node) { + std::vector> base_params; + base_params.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(base_params), + [](auto& node) { return node.property.base_sim.get_model(); }); - auto flows = std::vector>{}; - flows.reserve(results_graph.nodes().size()); - std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(flows), - [](auto&& node) { - auto& flow_node = node.property.base_sim.get_flows(); - auto interpolated_flows = mio::interpolate_simulation_result(flow_node); - return interpolated_flows; + std::vector> base_flows; + base_flows.reserve(results_graph.nodes().size()); + std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(base_flows), + [](auto& node) { + return mio::interpolate_simulation_result(node.property.base_sim.get_flows()); }); - // same for the mobility model (flows are enough here) - auto params_mobility = std::vector>(); - params_mobility.reserve(results_graph.nodes().size()); + // get mobility simulation results + std::vector> mobility_params; + mobility_params.reserve(results_graph.nodes().size()); std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), - std::back_inserter(params_mobility), [](auto&& node) { + std::back_inserter(mobility_params), [](auto& node) { return node.property.mobility_sim.get_model(); }); - auto flows_mobility = std::vector>{}; - flows_mobility.reserve(results_graph.nodes().size()); + std::vector> mobility_flows; + mobility_flows.reserve(results_graph.nodes().size()); std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), - std::back_inserter(flows_mobility), [](auto&& node) { - auto& flow_node = node.property.mobility_sim.get_flows(); - auto interpolated_flows = mio::interpolate_simulation_result(flow_node); - return interpolated_flows; + std::back_inserter(mobility_flows), [](auto& node) { + return mio::interpolate_simulation_result(node.property.mobility_sim.get_flows()); }); - std::cout << "Run " << run_idx << " complete." << std::endl; + if (mio::mpi::is_root()) { + std::cout << "Run " << run_idx << " complete.\n"; + } - return std::make_tuple(interpolated_result, params, flows, params_mobility, flows_mobility); + return std::make_tuple(base_results, base_params, base_flows, mobility_params, mobility_flows); }); - if (ensemble.size() > 0) { - auto ensemble_results = std::vector>>{}; + // Save results if ensemble is non-empty + if (!ensemble.empty()) { + std::vector>> ensemble_results, ensemble_flows, ensemble_flows_mobility; + std::vector>> ensemble_params, ensemble_params_mobility; + ensemble_results.reserve(ensemble.size()); - auto ensemble_params = std::vector>>{}; ensemble_params.reserve(ensemble.size()); - auto ensemble_flows = std::vector>>{}; ensemble_flows.reserve(ensemble.size()); - auto ensemble_params_mobility = std::vector>>{}; ensemble_params_mobility.reserve(ensemble.size()); - auto ensemble_flows_mobility = std::vector>>{}; ensemble_flows_mobility.reserve(ensemble.size()); + for (auto&& run : ensemble) { ensemble_results.emplace_back(std::move(std::get<0>(run))); ensemble_params.emplace_back(std::move(std::get<1>(run))); @@ -884,22 +856,26 @@ mio::IOResult run(const std::string data_dir, std::string res_dir, const i ensemble_flows_mobility.emplace_back(std::move(std::get<4>(run))); } + // Save base results BOOST_OUTCOME_TRY(mio::save_results(ensemble_results, ensemble_params, county_ids, res_dir, false)); - auto result_dir_run_flows = res_dir + "/flows"; + + // Save base flows + const std::string flows_dir = res_dir + "/flows"; if (mio::mpi::is_root()) { - boost::filesystem::create_directories(result_dir_run_flows); - printf("Saving Flow results to \"%s\".\n", result_dir_run_flows.c_str()); + fs::create_directories(flows_dir); + std::cout << "Saving flow results to \"" << flows_dir << "\".\n"; } - BOOST_OUTCOME_TRY(save_results(ensemble_flows, ensemble_params, county_ids, result_dir_run_flows, false)); + BOOST_OUTCOME_TRY(mio::save_results(ensemble_flows, ensemble_params, county_ids, flows_dir, false)); - auto result_dir_mobility = res_dir + "/mobility"; - auto result_dir_run_flows_mobility = result_dir_mobility + "/flows"; + // Save mobility flows + const std::string mobility_dir = res_dir + "/mobility"; + const std::string mobility_flows_dir = mobility_dir + "/flows"; if (mio::mpi::is_root()) { - boost::filesystem::create_directories(result_dir_run_flows_mobility); - printf("Saving Flow results to \"%s\".\n", result_dir_run_flows_mobility.c_str()); + fs::create_directories(mobility_flows_dir); + std::cout << "Saving mobility flow results to \"" << mobility_flows_dir << "\".\n"; } - BOOST_OUTCOME_TRY(save_results(ensemble_flows_mobility, ensemble_params_mobility, county_ids, - result_dir_run_flows_mobility, false)); + BOOST_OUTCOME_TRY(mio::save_results(ensemble_flows_mobility, ensemble_params_mobility, county_ids, + mobility_flows_dir, false)); } return mio::success(); @@ -914,45 +890,56 @@ int main(int argc, char** argv) std::string results_dir = ""; int num_days = 10; int num_runs = 2; + bool masks = true; + bool ffp2 = true; + bool edges = true; - if (argc == 5) { - // Full specification: + if (argc == 8) { data_dir = argv[1]; results_dir = argv[2]; - num_runs = atoi(argv[3]); - num_days = atoi(argv[4]); + num_runs = std::atoi(argv[3]); + num_days = std::atoi(argv[4]); + masks = std::atoi(argv[5]) != 0; + ffp2 = std::atoi(argv[6]) != 0; + edges = std::atoi(argv[7]) != 0; if (mio::mpi::is_root()) { printf("Reading data from \"%s\", saving results to \"%s\".\n", data_dir.c_str(), results_dir.c_str()); printf("Number of runs: %d, Number of days: %d.\n", num_runs, num_days); + printf("Masks: %s, FFP2: %s, Edges: %s.\n", masks ? "true" : "false", ffp2 ? "true" : "false", + edges ? "true" : "false"); } } - else if (argc == 3) { - // Partial specification: , use defaults for num_runs and num_days + else if (argc == 5) { data_dir = argv[1]; results_dir = argv[2]; + num_runs = std::atoi(argv[3]); + num_days = std::atoi(argv[4]); if (mio::mpi::is_root()) { printf("Reading data from \"%s\", saving results to \"%s\".\n", data_dir.c_str(), results_dir.c_str()); - printf("Using default values - Number of runs: %d, Number of days: %d.\n", num_runs, num_days); + printf("Using default values - Number of runs: %d, Number of days: %d, Masks: true, FFP2: true, Edges: " + "true.\n", + num_runs, num_days); } } else { if (mio::mpi::is_root()) { printf("Usage:\n"); - printf("2022_omicron_late_phase_mobility \n"); + printf("2022_omicron_late_phase_mobility " + "\n"); printf("\tRun simulation with data from , saving results to .\n"); printf("\t: Number of simulation runs.\n"); printf("\t: Number of days to simulate.\n"); - printf("2022_omicron_late_phase_mobility \n"); - printf("\tRun simulation with default num_runs=%d and num_days=%d.\n", num_runs, num_days); + printf("\t: 0 or 1 to disable or enable masks.\n"); + printf("\t: 0 or 1 to disable or enable FFP2 (requires masks).\n"); + printf("\t: 0 or 1 to disable or enable edges.\n"); } mio::mpi::finalize(); return 0; } - // Run the simulation - auto result = run(data_dir, results_dir, num_runs, num_days); + auto result = run(data_dir, results_dir, num_runs, num_days, masks, ffp2, edges); if (!result) { if (mio::mpi::is_root()) { printf("%s\n", result.error().formatted_message().c_str()); From 840c8f6d124dfd52f66860048a27bb6b08152619 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Thu, 13 Mar 2025 15:50:33 +0100 Subject: [PATCH 23/26] [ci skip] doc mobility, round --- cpp/memilio/compartments/simulation.h | 4 ++++ cpp/memilio/io/mobility_io.cpp | 6 ++++++ cpp/memilio/math/floating_point.h | 7 +++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cpp/memilio/compartments/simulation.h b/cpp/memilio/compartments/simulation.h index c81bb0740d..10a787b6b4 100644 --- a/cpp/memilio/compartments/simulation.h +++ b/cpp/memilio/compartments/simulation.h @@ -60,6 +60,10 @@ class Simulation { } + /** + * @brief Copy constructor + * @param[in] other Another simulation to copy + */ Simulation(const Simulation& other) : m_integratorCore(other.m_integratorCore) , m_model(std::make_unique(*other.m_model)) diff --git a/cpp/memilio/io/mobility_io.cpp b/cpp/memilio/io/mobility_io.cpp index 40f351c668..1dbdc111e0 100644 --- a/cpp/memilio/io/mobility_io.cpp +++ b/cpp/memilio/io/mobility_io.cpp @@ -160,14 +160,19 @@ IOResult read_mobility_plain(const std::string& filename) IOResult read_duration_stay(const std::string& filename) { + // count the number of lines in the file BOOST_OUTCOME_TRY(auto&& num_lines, count_lines(filename)); + // if the file is empty, return an empty matrix if (num_lines == 0) { return success(Eigen::MatrixXd(0, 0)); } + // open the file std::fstream file; file.open(filename, std::ios::in); + + // if the file could not be opened, return an error if (!file.is_open()) { return failure(StatusCode::FileNotFound, filename); } @@ -177,6 +182,7 @@ IOResult read_duration_stay(const std::string& filename) try { std::string tp; int linenumber = 0; + // read the file line by line while (getline(file, tp)) { auto line = split(tp, ' '); Eigen::Index i = static_cast(linenumber); diff --git a/cpp/memilio/math/floating_point.h b/cpp/memilio/math/floating_point.h index a6c08297c7..4a08ccecc7 100644 --- a/cpp/memilio/math/floating_point.h +++ b/cpp/memilio/math/floating_point.h @@ -143,12 +143,11 @@ bool floating_point_greater_equal(T v1, T v2, T abs_tol = 0, T rel_tol = std::nu * @param n The number of decimal places we want to round to. * @return The rounded double value to n decimal digits. */ -template +template T round_nth_decimal(T x, size_t n) { - using std::round; - T factor = std::pow(10.0, n); - return round(x * factor) / factor; + const T factor = std::pow(10.0, n); + return std::round(x * factor) / factor; } } // namespace mio From d00d83e1fcd630687d70fcd7e410c8ab1988c38c Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 14 Mar 2025 08:31:35 +0100 Subject: [PATCH 24/26] [ci skip] doc for extended graph --- .../metapopulation_mobility_detailed.h | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index c965e44f51..5d6e016705 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -48,12 +48,23 @@ namespace mio { +/** + * @brief Aggregates two simulation models and a stay duration for a node. + * + * @tparam Sim Type representing the simulation or model. + */ template struct ExtendedNodeProperty { - Sim base_sim; - Sim mobility_sim; - double stay_duration; + Sim base_sim; ///< The base simulation model representing local dynamics. + Sim mobility_sim; ///< The simulation model used for mobility-related processes. + double stay_duration; ///< Duration individuals stay in the node. + /** + * @brief Constructs an ExtendedNodeProperty with the given simulations and stay duration. + * @param sim1 The base simulation model. + * @param sim2 The mobility simulation model. + * @param t The stay duration. + */ ExtendedNodeProperty(Sim sim1, Sim sim2, double t) : base_sim(sim1) , mobility_sim(sim2) @@ -62,13 +73,25 @@ struct ExtendedNodeProperty { } }; +/** + * @brief Extends the basic MobilityEdge with travel time and a detailed travel path. + * + * @tparam FP Floating-point type used (default is double). + */ template class ExtendedMobilityEdge : public MobilityEdge { public: - double travel_time; - std::vector path; + double travel_time; ///< The travel time along this edge. + std::vector path; ///< A vector representing the travel path (e.g., sequence of node IDs). + /** + * @brief Constructs an ExtendedMobilityEdge using mobility parameters. + * + * @param params The mobility parameters used to initialize the base MobilityEdge. + * @param tt The travel time. + * @param p A vector representing the travel path. + */ ExtendedMobilityEdge(const MobilityParameters& params, double tt, std::vector p) : MobilityEdge(params) , travel_time(tt) @@ -76,6 +99,13 @@ class ExtendedMobilityEdge : public MobilityEdge { } + /** + * @brief Constructs an ExtendedMobilityEdge using a vector of coefficients. + * + * @param coeffs A vector of mobility coefficients. + * @param tt The travel time. + * @param p A vector representing the travel path. + */ ExtendedMobilityEdge(const Eigen::VectorXd& coeffs, double tt, std::vector p) : MobilityEdge(coeffs) , travel_time(tt) @@ -83,14 +113,31 @@ class ExtendedMobilityEdge : public MobilityEdge { } + /** + * @brief Returns a reference to the mobile population that migrated along this edge. + * + * @return A reference to the migrated mobile population. + */ auto& get_migrated() { return this->m_mobile_population; } + + /** + * @brief Returns a reference to the return times associated with this edge. + * + * @return A reference to the vector containing the return times. + */ auto& get_return_times() { return this->m_return_times; } + + /** + * @brief Returns a const reference to the mobility parameters associated with this edge. + * + * @return A const reference to the mobility parameters. + */ auto& get_parameters() const { return this->m_parameters; From 378c91af48a6e6c689a8ee0b6b808a0710edcc21 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:40:59 +0100 Subject: [PATCH 25/26] rework scheduler --- .../metapopulation_mobility_detailed.h | 579 +++++++++++------- 1 file changed, 341 insertions(+), 238 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 5d6e016705..577a5a5602 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -154,23 +154,41 @@ auto get_mobility_factors(const Sim& /*sim*/, double /*t*/, const Eigen::Ref void check_negative_values_vec(Eigen::Ref> vec, const size_t num_age_groups, FP tolerance = -1e-7, const size_t max_iterations = 100) { - // before moving the commuters, we need to look for negative values in vec and correct them. + // Determine the number of compartments per age group. const size_t num_comparts = vec.size() / num_age_groups; size_t iteration_count = 0; - // check for negative values in vec + // Loop until no negative values (below tolerance) remain in the vector. while (vec.minCoeff() < tolerance) { Eigen::Index min_index; + // Get the minimum value in the vector and its index. const FP min_value = vec.minCoeff(&min_index); + // Determine the current age group based on the index. const auto curr_age_group = min_index / num_comparts; - const auto indx_begin = curr_age_group * num_comparts; - const auto indx_end = indx_begin + num_comparts; + // Compute the range of indices for the current age group. + const auto indx_begin = curr_age_group * num_comparts; + const auto indx_end = indx_begin + num_comparts; + // Find the index of the maximum value within the same age group. auto max_group_indx = indx_begin; for (auto i = indx_begin; i < indx_end; ++i) { if (vec(i) > vec(max_group_indx)) { @@ -178,14 +196,13 @@ void check_negative_values_vec(Eigen::Ref> vec, const size_t } } - // Correct the negative value + // Correct the negative value by setting it to zero and + // adding its (negative) value to the maximum value of the group. vec(min_index) = 0; vec(max_group_indx) += min_value; - // check if the number of iterations is exceeded + // Check if the iteration count exceeds the allowed maximum. if (iteration_count > max_iterations) { - std::vector vec_std(vec.data(), vec.data() + vec.size()); - mio::unused(vec_std); log_error("Number of iterations exceeded in check_negative_values_vec."); std::exit(1); } @@ -193,32 +210,55 @@ void check_negative_values_vec(Eigen::Ref> vec, const size_t } } +/** + * @brief Finds the time index corresponding to a given time point in the simulation. + * + * This function searches for a time point (t) in the simulation's results. + * It compares the provided time with each time point stored in the simulation (starting from the latest) + * until it finds one that differs from t by less than 1e-10. If the time point is not found and + * create_new_tp is true, a new time point is added to the results and flows. Otherwise, an error is logged. + * + * @tparam FP Floating-point type used. + * @tparam Sim Simulation type that has access to results and flows. + * @param simulation The simulation containing the results and flows. + * @param t The time point to search for. + * @param create_new_tp Flag indicating whether to create a new time point if t is not found. + * @return Eigen::Index The index corresponding to the time point. + */ template Eigen::Index find_time_index(Sim& simulation, FP t, bool create_new_tp) { + // Get references to simulation results and flows. auto& results = simulation.get_result(); auto& flows = simulation.get_flows(); + + // Ensure that the number of time points in results and flows match. if (results.get_num_time_points() != flows.get_num_time_points()) { - log_error("Number of time points in results " + std::to_string(results.get_num_time_points()) + " and flows " + - std::to_string(flows.get_num_time_points()) + " do not match in find_time_index."); + log_error("Number of time points in results (" + std::to_string(results.get_num_time_points()) + + ") and flows (" + std::to_string(flows.get_num_time_points()) + ") do not match in find_time_index."); } + + // Start from the last time point index. Eigen::Index t_indx = results.get_num_time_points() - 1; + // Iterate backwards until a time point is found that matches t within a small tolerance. for (; t_indx >= 0; --t_indx) { if (std::abs(results.get_time(t_indx) - t) < 1e-10) { break; } } + // If no matching time point is found and create_new_tp is false, log an error. if (t_indx < 0 && !create_new_tp) { - log_error("Time point " + std::to_string(t) + " not found in find_time_index. Lates time point is " + + log_error("Time point " + std::to_string(t) + " not found in find_time_index. Latest time point is " + std::to_string(results.get_last_time())); } + // If t is greater than the last time point and create_new_tp is enabled, create a new time point. if (t_indx < 0 && results.get_last_time() < t && create_new_tp) { - // if we allow to create a new time point, we initialize the compartments with zero - // the flows are accumulated. Therefore, we can just copy the last value. + // Initialize a new result vector with zeros. Eigen::VectorXd results_vec = Eigen::VectorXd::Zero(results.get_last_value().size()); results.add_time_point(t, results_vec); + // For flows, copy the last value (since flows are accumulated). flows.add_time_point(t, flows.get_last_value()); t_indx = results.get_num_time_points() - 1; } @@ -226,61 +266,108 @@ Eigen::Index find_time_index(Sim& simulation, FP t, bool create_new_tp) return t_indx; } +/** + * @brief Provides mobility-related functions for simulation models. + + * @tparam FP Floating-point type used. + * @tparam Sim Simulation type that has access to results, flows, and model. + */ template class MobilityFunctions { public: - void init_mobility(FP t, ExtendedMobilityEdge& edge, Sim& from_sim, Sim& to_sim) + /** + * @brief Initializes mobility for a given edge at time t. + * + * This function finds the starting time point in the source simulation, calculates the number + * of commuters to be moved based on the simulation results and mobility coefficients, and adds + * a corresponding time point to the return times of the edge. It then moves the commuters from the + * source simulation to the target simulation. + * + * @param t The current time. + * @param edge The extended mobility edge to be initialized. + * @param sim_source The source simulation from which commuters are taken. + * @param sim_target The target simulation to which commuters are added. + */ + void init_mobility(FP t, ExtendedMobilityEdge& edge, Sim& sim_source, Sim& sim_target) { - const auto t_indx_start_mobility_sim_from = find_time_index(from_sim, t, false); + // Find the time index in the source simulation without creating a new time point. + const auto t_indx_start_mobility_sim_from = find_time_index(sim_source, t, false); - // initialize the number of commuters at the start of the mobility - auto results_from = from_sim.get_result(); + // Initialize the number of commuters at the start of mobility by computing + // a weighted product of the result, mobility coefficients and mobility factors. + auto results_from = sim_source.get_result(); edge.get_migrated().add_time_point( t, (results_from.get_value(t_indx_start_mobility_sim_from).array() * edge.get_parameters().get_coefficients().get_matrix_at(t).array() * - get_mobility_factors(from_sim, t, results_from.get_value(t_indx_start_mobility_sim_from)).array()) + get_mobility_factors(sim_source, t, results_from.get_value(t_indx_start_mobility_sim_from)).array()) .matrix()); + // Save the time point for the return times. edge.get_return_times().add_time_point(t); - // move them to the starting mobility model - // if the simulation we are adding the commuters to is not having the same time point as the current time point, - // we need to add a new time point to the simulation. - const auto t_indx_start_mobility_sim_to = find_time_index(to_sim, t, true); - to_sim.get_result().get_value(t_indx_start_mobility_sim_to) += edge.get_migrated().get_last_value(); - from_sim.get_result().get_last_value() -= edge.get_migrated().get_last_value(); + // Move commuters to the target simulation. + // If the target simulation does not have the time point, create one. + const auto t_indx_start_mobility_sim_to = find_time_index(sim_target, t, true); + sim_target.get_result().get_value(t_indx_start_mobility_sim_to) += edge.get_migrated().get_last_value(); + sim_source.get_result().get_last_value() -= edge.get_migrated().get_last_value(); } - void move_migrated(FP t, ExtendedMobilityEdge& edge, Sim& from_sim, Sim& to_sim) + /** + * @brief Moves the migrated population from one simulation to another. + * + * This function transfers commuters from the source simulation to the target simulation, + * updating the respective result vectors. It also calls check_negative_values_vec to ensure + * that no negative values remain in the population vectors after moving. + * + * @param t The current time. + * @param edge The extended mobility edge holding the migrated population. + * @param sim_source The simulation from which commuters are removed. + * @param sim_target The simulation to which commuters are added. + */ + void move_migrated(FP t, ExtendedMobilityEdge& edge, Sim& sim_source, Sim& sim_target) { - // When moving from one regional entity/model to another, we need to update the local population. - // check_negative_values_vec needs to be called once since its checks for negative values and corrects them. - const size_t num_age_groups = static_cast(from_sim.get_model().parameters.get_num_groups()); + // Determine the number of age groups from the simulation's model parameters. + const size_t num_age_groups = static_cast(sim_source.get_model().parameters.get_num_groups()); + // Correct any negative values in the migrated population vector. check_negative_values_vec(edge.get_migrated().get_last_value(), num_age_groups); - const auto t_indx_sim_to_arrival = find_time_index(to_sim, t, true); - from_sim.get_result().get_last_value() -= edge.get_migrated().get_last_value(); - to_sim.get_result().get_value(t_indx_sim_to_arrival) += edge.get_migrated().get_last_value(); - // check each result for negative values and correct them if necessary - check_negative_values_vec(from_sim.get_result().get_last_value(), num_age_groups); - check_negative_values_vec(to_sim.get_result().get_value(t_indx_sim_to_arrival), num_age_groups); + // Find the arrival time index in the target simulation, creating a new time point if needed. + const auto t_indx_sim_to_arrival = find_time_index(sim_target, t, true); + // Remove the migrated population from the source simulation. + sim_source.get_result().get_last_value() -= edge.get_migrated().get_last_value(); + // Add the migrated population to the target simulation. + sim_target.get_result().get_value(t_indx_sim_to_arrival) += edge.get_migrated().get_last_value(); + + // Re-check both simulations for negative values after the move. + check_negative_values_vec(sim_source.get_result().get_last_value(), num_age_groups); + check_negative_values_vec(sim_target.get_result().get_value(t_indx_sim_to_arrival), num_age_groups); } + /** + * @brief Updates the status of commuters (migrated population) in the simulation. + * + * @param t The current time. + * @param dt The time increment for updating commuters. + * @param edge The extended mobility edge holding the migrated population. + * @param sim The simulation whose mobility model is being updated. + * @param is_mobility_model Flag indicating whether the simulation is a mobility model. + */ void update_commuters(FP t, FP dt, ExtendedMobilityEdge& edge, Sim& sim, bool is_mobility_model) { + // Find or create the time point for the current time in the simulation. const auto t_indx_start_mobility_sim = find_time_index(sim, t, true); - Eigen::VectorXd flows = Eigen::VectorXd::Zero(sim.get_flows().get_last_value().size()); + // Initialize flows vector with zeros. + Eigen::VectorXd flows = Eigen::VectorXd::Zero(sim.get_flows().get_last_value().size()); + // Update the status of migrated commuters and update flows accordingly. update_status_migrated(edge.get_migrated().get_last_value(), sim, sim.get_result().get_value(t_indx_start_mobility_sim), t, dt, flows); - // if the simulation is holding a mobility model, we need to update the mobility model as well - // Therefore, we check if the time point already exists in the mobility model and create a new one if necessary. - // Next, we build the population in the mobility model based on the commuters + // If the simulation holds a mobility model, update it by creating a new time point if necessary. if (is_mobility_model) { const auto t_indx_mobility_model = find_time_index(sim, t + dt, true); if (t_indx_mobility_model != sim.get_result().get_num_time_points() - 1) { log_error("Time point " + std::to_string(t + dt) + - " not the lastest in update_commuters. Latest time point is " + + " not the latest in update_commuters. Latest time point is " + std::to_string(sim.get_result().get_last_time())); } sim.get_result().get_value(t_indx_mobility_model) += edge.get_migrated().get_last_value(); @@ -288,18 +375,38 @@ class MobilityFunctions } } + /** + * @brief Deletes all time points from the migrated and return times of an edge. + * + * @param edge The extended mobility edge whose time points are to be removed. + */ void delete_migrated(ExtendedMobilityEdge& edge) { + // Remove time points in reverse order to avoid index shifting issues. for (Eigen::Index i = edge.get_return_times().get_num_time_points() - 1; i >= 0; --i) { edge.get_migrated().remove_time_point(i); edge.get_return_times().remove_time_point(i); } } }; - +/** + * @brief Manages the computation of schedules for a graph's edges and nodes. + */ class ScheduleManager { public: + /** + * @brief Holds all schedule-related vectors. + * Exlanation of the vectors: + * - "schedule_edges": For each edge, this vector contains the node index where an individual is located at each timestep for a day. + * - "mobility_schedule_edges": For each edge and each timestep, a boolean flag indicating if the individual is in a mobility model. + * - "mobility_int_schedule": For each node, a vector of timesteps at which the mobility model needs to be synchronized. + * - "local_int_schedule": For each node, a vector of timesteps at which the local model needs to be synchronized. + * - "edges_mobility": For each timestep, a list of edge indices where mobility is happening. + * - "nodes_mobility": For each timestep, a list of node indices where the local model is exchanging individuals. + * - "nodes_mobility_m": For each timestep, a list of node indices where the mobility model is exchanging individuals + * - "first_mobility": For each edge, the first timestep at which mobility occurs. + */ struct Schedule { std::vector> schedule_edges; std::vector> mobility_schedule_edges; @@ -311,12 +418,25 @@ class ScheduleManager std::vector first_mobility; }; - ScheduleManager(size_t t, double eps = 1e-10) - : timesteps(t) + /** + * @brief Construct a new ScheduleManager object. + * @param n_t Total number of timesteps. + * @param eps A epsilon as tolerance. + */ + ScheduleManager(size_t n_t, double eps = 1e-10) + : n_timesteps(n_t) , epsilon(eps) { } + /** + * @brief Compute the complete schedule for the given graph. + * + * This function calculates both the edge schedule and the node schedule. + * @tparam Graph Type of the graph. + * @param graph The graph object containing nodes and edges. + * @return Schedule The computed schedule. + */ template Schedule compute_schedule(const Graph& graph) { @@ -326,16 +446,56 @@ class ScheduleManager return schedule; } +private: + size_t n_timesteps; ///< Total number of timesteps. + double epsilon; ///< Tolerance + /** - * @brief Calculates the edge schedule for a given graph. - * - * This function computes the vectors schedule_edges and mobility_schedule_edges for each edge in the graph. - * The schedule_eges describes for each time step in which node_id the individuals are located. - * The mobility_schedule_edges describes for each time step if the individuals are in a mobility node or not. - * - * @tparam Graph The type of the graph. - * @param graph The graph object containing the nodes and edges. - * @param schedule The Schedule object to store the computed schedules. + * @brief Fills a range within a vector with a specified value. + * @tparam T The type of elements in the vector. + * @param vec The vector to fill. + * @param start The starting index. + * @param end The ending index. + * @param value The value to fill with. + */ + template + void fill_vector_range(std::vector& vec, size_t start, size_t end, const T& value) + { + if (start < vec.size() && end <= vec.size() && start < end) { + std::fill(vec.begin() + start, vec.begin() + end, value); + } + } + + /** + * @brief Get indices of edges while iterating over all edges in the provided schedule and selects those edges + * for which the node index at timestep t is equal to node_Idx, and the corresponding mobility flag matches the mobility parameter. + * + * @param schedule The precomputed schedule containing the node positions and mobility flags for each edge. + * @param node_Idx The index of the node to check against the edge schedules. + * @param t The timestep at which to evaluate the edge positions and mobility states. + * @param mobility The mobility flag to match; set to true to select edges in mobility mode, false otherwise. + * @return std::vector A vector containing the indices of edges that satisfy both conditions. + */ + std::vector find_edges_at_time(const Schedule& schedule, size_t node_idx, size_t t, bool mobility) const + { + std::vector edges; + // Loop over all edges in the schedule. + for (size_t edge_idx = 0; edge_idx < schedule.schedule_edges.size(); ++edge_idx) { + // Find edges located at node_idx at timestep t with the specified mobility flag. + if (schedule.schedule_edges[edge_idx][t] == node_idx && + schedule.mobility_schedule_edges[edge_idx][t] == mobility) { + edges.push_back(edge_idx); + } + } + return edges; + } + + /** + * @brief Calculates the schedule for all edges in the graph. + * + * @tparam Graph Type of the graph. + * @param graph The graph object. + * @param schedule The schedule object to populate. */ template void calculate_edge_schedule(const Graph& graph, Schedule& schedule) @@ -343,247 +503,190 @@ class ScheduleManager schedule.schedule_edges.reserve(graph.edges().size()); schedule.mobility_schedule_edges.reserve(graph.edges().size()); - // calculate the schedule for each edge for (auto& e : graph.edges()) { - std::vector tmp_schedule(timesteps, 0); - std::vector is_mobility_node(timesteps, false); + std::vector edge_schedule(n_timesteps, 0); + std::vector mobility_flag(n_timesteps, false); - // Calculate travel time per region, ensuring a minimum value of 0.01 - const double traveltime_per_region = + // Calculate travel time per region (minimum 0.01) + const double travel_time_per_region = std::max(0.01, round_nth_decimal(e.property.travel_time / e.property.path.size(), 2)); - // Calculate the start time for mobility, ensuring it greater or equal to 0.01 + // Calculate start time for mobility (minimum 0.01) const double start_mobility = - std::max(0.01, round_nth_decimal(1 - 2 * traveltime_per_region * e.property.path.size() - + std::max(0.01, round_nth_decimal(1 - 2 * travel_time_per_region * e.property.path.size() - graph.nodes()[e.end_node_idx].property.stay_duration, 2)); - // Calculate the arrival time at the destination node - const double arrive_at = start_mobility + traveltime_per_region * e.property.path.size(); + // Calculate arrival time at destination + const double arrival_time = start_mobility + travel_time_per_region * e.property.path.size(); - // Lambda to fill the schedule vector with the node index during the trip to the destination. - auto fill_schedule = [&](size_t start_idx, size_t end_idx, size_t value) { - std::fill(tmp_schedule.begin() + start_idx, tmp_schedule.begin() + end_idx, value); - }; + // Convert times to indices + const size_t start_index = static_cast((start_mobility + epsilon) * 100); + const size_t arrival_index = static_cast((arrival_time + epsilon) * 100); + const size_t end_index = n_timesteps - (arrival_index - start_index); - // Lambda to fill the schedule for the mobility models with a bool during the trip to the destination. - auto fill_mobility = [&](size_t start_idx, size_t end_idx, bool value) { - std::fill(is_mobility_node.begin() + start_idx, is_mobility_node.begin() + end_idx, value); - }; + // Fill schedule before mobility with start node index. + fill_vector_range(edge_schedule, 0, start_index, e.start_node_idx); - // Indices for schedule filling - const size_t start_idx = static_cast((start_mobility + epsilon) * 100); - const size_t arrive_idx = static_cast((arrive_at + epsilon) * 100); - const size_t stay_end_idx = timesteps - (arrive_idx - start_idx); + // Fill mobility flag during mobility period. + fill_vector_range(mobility_flag, start_index, arrival_index, true); - // Fill the schedule up to the start of mobility with the start node index - fill_schedule(0, start_idx, e.start_node_idx); - - // Mark the mobility period in the mobility node vector - fill_mobility(start_idx, arrive_idx, true); - - // Fill the schedule for the path during the mobility period - size_t current_index = start_idx; + // Fill schedule during mobility along the given path. + size_t current_index = start_index; for (size_t county : e.property.path) { - size_t next_index = current_index + static_cast((traveltime_per_region + epsilon) * 100); - fill_schedule(current_index, next_index, county); + size_t next_index = current_index + static_cast((travel_time_per_region + epsilon) * 100); + fill_vector_range(edge_schedule, current_index, next_index, county); current_index = next_index; } - // Fill the remaining schedule after mobility with the end node index - fill_schedule(current_index, stay_end_idx, e.property.path.back()); - - // Mark the return mobility period in the mobility node vector - fill_mobility(stay_end_idx, timesteps, true); - - // Find the first and last true values in the mobility node vector - auto first_true = std::find(is_mobility_node.begin(), is_mobility_node.end(), true); - auto last_true = std::find(is_mobility_node.rbegin(), is_mobility_node.rend(), true); - - // Ensure there is at least one true value - if (first_true != is_mobility_node.end() && last_true != is_mobility_node.rend()) { - size_t first_index = std::distance(is_mobility_node.begin(), first_true); - size_t count_true = arrive_idx - start_idx; - - // Create a reversed path segment for the return trip - std::vector path_reversed(tmp_schedule.begin() + first_index, - tmp_schedule.begin() + first_index + count_true); - std::reverse(path_reversed.begin(), path_reversed.end()); - - // Copy the reversed path segment to the end of the schedule for the return trip - std::copy(path_reversed.begin(), path_reversed.end(), tmp_schedule.end() - count_true); - - // Add the schedule and mobility node vectors to their respective containers - schedule.schedule_edges.push_back(std::move(tmp_schedule)); - schedule.mobility_schedule_edges.push_back(std::move(is_mobility_node)); + // Fill remaining schedule after mobility with destination node. + fill_vector_range(edge_schedule, current_index, end_index, e.property.path.back()); + + // Fill return mobility period after staying. + fill_vector_range(mobility_flag, end_index, n_timesteps, true); + + // Reverse the mobility for the return trip. + auto first_true = std::find(mobility_flag.begin(), mobility_flag.end(), true); + auto last_true = std::find(mobility_flag.rbegin(), mobility_flag.rend(), true); + if (first_true != mobility_flag.end() && last_true != mobility_flag.rend()) { + size_t first_index_found = std::distance(mobility_flag.begin(), first_true); + size_t mobility_duration = arrival_index - start_index; + std::vector reversed_path(edge_schedule.begin() + first_index_found, + edge_schedule.begin() + first_index_found + mobility_duration); + std::reverse(reversed_path.begin(), reversed_path.end()); + std::copy(reversed_path.begin(), reversed_path.end(), edge_schedule.end() - mobility_duration); + + // Store the computed schedules. + schedule.schedule_edges.push_back(std::move(edge_schedule)); + schedule.mobility_schedule_edges.push_back(std::move(mobility_flag)); } else { - log_error("Error in creating schedule."); + log_error("Error in creating schedule for an edge."); } } } /** - * @brief Calculates the node schedule for a given graph. - * - * This function computes the vectors local_int_schedule, mobility_int_schedule, edges_mobility, nodes_mobility, - * nodes_mobility_m and first_mobility. - * The local_int_schedule vector describes the time steps where we need to synchronize the integrator in the local models. - * The mobility_int_schedule vector describes the time steps where we need to synchronize the integrator in the mobility models. - * The edges_mobility vector describes all edges where mobility takes place for each time step. - * The nodes_mobility vector describes all local models where mobility takes place for each time step. - * The nodes_mobility_m vector describes all mobility models where mobility takes place at the beginning of each time step. - * The first_mobility vector describes the first time step where mobility takes place for each edge. - * - * @tparam Graph The type of the graph object. - * @param graph The graph object containing the nodes and edges. - * @param schedule The Schedule object to store the computed schedules. + * @brief Calculates the node schedule based on edge schedules. + * + * @tparam Graph Type of the graph. + * @param graph The graph object. + * @param schedule The schedule object to populate. */ template void calculate_node_schedule(const Graph& graph, Schedule& schedule) { - - // iterate over nodes to create the integration schedule for each node. The integration schedule is necessary - // to determine the correct time step for the integrator in the nodes. - for (size_t indx_node = 0; indx_node < graph.nodes().size(); ++indx_node) { - // Local node initialization with starting at t=0 - std::vector order_local_node = {0}; - std::vector indx_edges; - - // Find edges starting from the current node and not in mobility at t=0 - auto find_edges = [&](size_t t, bool mobility) { - std::vector edges; - for (size_t indx_edge = 0; indx_edge < schedule.schedule_edges.size(); ++indx_edge) { - if (schedule.schedule_edges[indx_edge][t] == indx_node && - schedule.mobility_schedule_edges[indx_edge][t] == mobility) { - edges.push_back(indx_edge); - } - } - return edges; - }; - - indx_edges = find_edges(0, false); - - // Iterate through each timestep to identify changes in local node schedule - for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { - auto indx_edges_new = find_edges(indx_t, false); - - if (indx_edges_new.size() != indx_edges.size() || - !std::equal(indx_edges.begin(), indx_edges.end(), indx_edges_new.begin())) { - order_local_node.push_back(indx_t); - indx_edges = indx_edges_new; + // Compute integration schedules per node. + for (size_t node_idx = 0; node_idx < graph.nodes().size(); ++node_idx) { + // Local integration schedule. + std::vector local_schedule{0}; // Always start at t=0. + auto current_edges = find_edges_at_time(schedule, node_idx, 0, false); + for (size_t t = 1; t < n_timesteps; ++t) { + auto new_edges = find_edges_at_time(schedule, node_idx, t, false); + if (new_edges.size() != current_edges.size() || + !std::equal(current_edges.begin(), current_edges.end(), new_edges.begin())) { + local_schedule.push_back(t); + current_edges = new_edges; } } - - // Ensure the last timestep is included - if (order_local_node.back() != timesteps - 1) { - order_local_node.push_back(timesteps - 1); + if (local_schedule.back() != n_timesteps - 1) { + local_schedule.push_back(n_timesteps - 1); } - schedule.local_int_schedule.push_back(order_local_node); - - // Mobility node initialization - std::vector order_mobility_node; - std::vector indx_edges_mobility; - - indx_edges_mobility = find_edges(0, true); - - // Iterate through each timestep to identify changes in mobility node schedule - for (size_t indx_t = 1; indx_t < timesteps; ++indx_t) { - auto indx_edges_mobility_new = find_edges(indx_t, true); - - if (indx_edges_mobility_new.size() != indx_edges_mobility.size() || - !std::equal(indx_edges_mobility.begin(), indx_edges_mobility.end(), - indx_edges_mobility_new.begin())) { - order_mobility_node.push_back(indx_t); - indx_edges_mobility = indx_edges_mobility_new; + schedule.local_int_schedule.push_back(local_schedule); + + // Mobility integration schedule. + std::vector mobility_schedule; + auto current_mob_edges = find_edges_at_time(schedule, node_idx, 0, true); + for (size_t t = 1; t < n_timesteps; ++t) { + auto new_mob_edges = find_edges_at_time(schedule, node_idx, t, true); + if (new_mob_edges.size() != current_mob_edges.size() || + !std::equal(current_mob_edges.begin(), current_mob_edges.end(), new_mob_edges.begin())) { + mobility_schedule.push_back(t); + current_mob_edges = new_mob_edges; } } - - schedule.mobility_int_schedule.push_back(order_mobility_node); + schedule.mobility_int_schedule.push_back(mobility_schedule); } - // Reserve space for first_mobility vector and initialize it + // Determine the first timestep with mobility for each edge. schedule.first_mobility.reserve(graph.edges().size()); - for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { - // find the first time step where mobility takes place. If there is no mobility, it is set the size of the schedule. - size_t index_time = 0; - for (; index_time < schedule.mobility_schedule_edges[indx_edge].size(); ++index_time) { - if (schedule.mobility_schedule_edges[indx_edge][index_time]) { + for (size_t edge_idx = 0; edge_idx < graph.edges().size(); ++edge_idx) { + size_t t = 0; + for (; t < schedule.mobility_schedule_edges[edge_idx].size(); ++t) { + if (schedule.mobility_schedule_edges[edge_idx][t]) { break; } } - schedule.first_mobility.push_back(index_time); + schedule.first_mobility.push_back(t); } - // Reserve space for mobility-related vectors - schedule.edges_mobility.reserve(timesteps); - schedule.nodes_mobility.reserve(timesteps); - schedule.nodes_mobility_m.reserve(timesteps); + // Initialize mobility-related vectors per timestep. + schedule.edges_mobility.reserve(n_timesteps); + schedule.nodes_mobility.reserve(n_timesteps); + schedule.nodes_mobility_m.reserve(n_timesteps); - // Handle the case where indx_current = 0 separately - std::vector temp_edges_mobility; - for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { - if (schedule.first_mobility[indx_edge] == 0) { - temp_edges_mobility.push_back(indx_edge); + // At t = 0: collect edges with mobility starting at 0. + std::vector initial_edges; + for (size_t edge_idx = 0; edge_idx < graph.edges().size(); ++edge_idx) { + if (schedule.first_mobility[edge_idx] == 0) { + initial_edges.push_back(edge_idx); } } - schedule.edges_mobility.push_back(std::move(temp_edges_mobility)); - - // Initialize nodes_mobility with all node indices for t=0 - std::vector temp_nodes_mobility(graph.nodes().size()); - std::iota(temp_nodes_mobility.begin(), temp_nodes_mobility.end(), 0); - schedule.nodes_mobility.emplace_back(std::move(temp_nodes_mobility)); - - // Initialize nodes_mobility_m with nodes that have mobility activities at t=0 - std::vector temp_nodes_mobility_m; - for (size_t node_indx = 0; node_indx < graph.nodes().size(); ++node_indx) { - if (std::binary_search(schedule.mobility_int_schedule[node_indx].begin(), - schedule.mobility_int_schedule[node_indx].end(), 0)) { - temp_nodes_mobility_m.push_back(node_indx); + schedule.edges_mobility.push_back(std::move(initial_edges)); + + // At t = 0: initialize with all nodes. + std::vector initial_nodes(graph.nodes().size()); + std::iota(initial_nodes.begin(), initial_nodes.end(), 0); + schedule.nodes_mobility.push_back(std::move(initial_nodes)); + + // At t = 0: nodes with mobility activity (if present in mobility_int_schedule). + std::vector initial_nodes_mob_m; + for (size_t node_idx = 0; node_idx < graph.nodes().size(); ++node_idx) { + if (std::binary_search(schedule.mobility_int_schedule[node_idx].begin(), + schedule.mobility_int_schedule[node_idx].end(), 0)) { + initial_nodes_mob_m.push_back(node_idx); } } - schedule.nodes_mobility_m.push_back(temp_nodes_mobility_m); - - for (size_t indx_current = 1; indx_current < timesteps; ++indx_current) { - std::vector temp_edge_mobility; - for (size_t indx_edge = 0; indx_edge < graph.edges().size(); ++indx_edge) { - size_t current_node_indx = schedule.schedule_edges[indx_edge][indx_current]; - if (indx_current >= schedule.first_mobility[indx_edge]) { - if (schedule.mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(schedule.mobility_int_schedule[current_node_indx].begin(), - schedule.mobility_int_schedule[current_node_indx].end(), indx_current)) { - temp_edge_mobility.push_back(indx_edge); + schedule.nodes_mobility_m.push_back(std::move(initial_nodes_mob_m)); + + // For each subsequent timestep, update mobility edge and node lists. + for (size_t t = 1; t < n_timesteps; ++t) { + // Identify mobility-active edges at timestep t. + std::vector edges_at_t; + for (size_t edge_idx = 0; edge_idx < graph.edges().size(); ++edge_idx) { + size_t current_node = schedule.schedule_edges[edge_idx][t]; + if (t >= schedule.first_mobility[edge_idx]) { + if (schedule.mobility_schedule_edges[edge_idx][t] && + std::binary_search(schedule.mobility_int_schedule[current_node].begin(), + schedule.mobility_int_schedule[current_node].end(), t)) { + edges_at_t.push_back(edge_idx); } - else if (!schedule.mobility_schedule_edges[indx_edge][indx_current] && - std::binary_search(schedule.local_int_schedule[current_node_indx].begin(), - schedule.local_int_schedule[current_node_indx].end(), indx_current)) { - temp_edge_mobility.push_back(indx_edge); + else if (!schedule.mobility_schedule_edges[edge_idx][t] && + std::binary_search(schedule.local_int_schedule[current_node].begin(), + schedule.local_int_schedule[current_node].end(), t)) { + edges_at_t.push_back(edge_idx); } } } - schedule.edges_mobility.push_back(temp_edge_mobility); - - // Clear and fill temp_nodes_mobility and temp_nodes_mobility_m for current timestep - temp_nodes_mobility.clear(); - temp_nodes_mobility_m.clear(); - for (size_t node_indx = 0; node_indx < graph.nodes().size(); ++node_indx) { - if (std::binary_search(schedule.local_int_schedule[node_indx].begin(), - schedule.local_int_schedule[node_indx].end(), indx_current)) { - temp_nodes_mobility.push_back(node_indx); + schedule.edges_mobility.push_back(edges_at_t); + + // Identify nodes with local and mobility integration at timestep t. + std::vector nodes_local; + std::vector nodes_mob_m; + for (size_t node_idx = 0; node_idx < graph.nodes().size(); ++node_idx) { + if (std::binary_search(schedule.local_int_schedule[node_idx].begin(), + schedule.local_int_schedule[node_idx].end(), t)) { + nodes_local.push_back(node_idx); } - - if (std::binary_search(schedule.mobility_int_schedule[node_indx].begin(), - schedule.mobility_int_schedule[node_indx].end(), indx_current)) { - temp_nodes_mobility_m.push_back(node_indx); + if (std::binary_search(schedule.mobility_int_schedule[node_idx].begin(), + schedule.mobility_int_schedule[node_idx].end(), t)) { + nodes_mob_m.push_back(node_idx); } } - schedule.nodes_mobility.push_back(temp_nodes_mobility); - schedule.nodes_mobility_m.push_back(temp_nodes_mobility_m); + schedule.nodes_mobility.push_back(nodes_local); + schedule.nodes_mobility_m.push_back(nodes_mob_m); } } - - size_t timesteps; - double epsilon; }; template From d482976afa51d979b6d5f06f33c6fd77baad89ec Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 14 Mar 2025 15:02:25 +0100 Subject: [PATCH 26/26] update graph sim --- .../metapopulation_mobility_detailed.h | 335 +++++++++++------- 1 file changed, 204 insertions(+), 131 deletions(-) diff --git a/cpp/memilio/mobility/metapopulation_mobility_detailed.h b/cpp/memilio/mobility/metapopulation_mobility_detailed.h index 577a5a5602..120ec159a0 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_detailed.h +++ b/cpp/memilio/mobility/metapopulation_mobility_detailed.h @@ -689,6 +689,15 @@ class ScheduleManager } }; +/** + * @brief Extended graph simulation class that adds mobility models. + * + * This class extends the base graph simulation by incorporating mobility-specific functions. + * It computes schedules for edge and node mobility and advances the simulation in discrete time steps. + * + * @tparam Graph Type of the graph. + * @tparam MobilityFunctions Type providing mobility-related functions. + */ template class GraphSimulationExtended : public GraphSimulationBase; + /** + * @brief Construct a new GraphSimulationExtended object. + * + * @param t0 Initial simulation time. + * @param dt Time step size. + * @param g Reference to the graph. + * @param node_func Function to update nodes. + * @param modes Mobility functions object. + */ GraphSimulationExtended(double t0, double dt, Graph& g, const node_function& node_func, MobilityFunctions modes) : GraphSimulationBasem_graph); } + /** + * @brief Construct a new GraphSimulationExtended object. + * + * @param t0 Initial simulation time. + * @param dt Time step size. + * @param g Graph to be moved. + * @param node_func Function to update nodes. + * @param modes Mobility functions object. + */ GraphSimulationExtended(double t0, double dt, Graph&& g, const node_function& node_func, MobilityFunctions modes) : GraphSimulationBase(g), node_func, {}) , m_mobility_functions(modes) { - ScheduleManager schedule_manager(100); // Assuming 100 timesteps + ScheduleManager schedule_manager(100); // Assume 100 timesteps. schedules = schedule_manager.compute_schedule(this->m_graph); } + /** + * @brief Advances the simulation until time t_max. + * + * The simulation proceeds in three steps per time interval: + * 1. Moving commuters between nodes. + * 2. Advancing the local node integrators. + * 3. Updating the status of commuters and mobility models. + * + * At the end of each day, populations in mobility nodes are reset and, for long simulations, the time series + * are interpolated to save memory. + * + * @param t_max Maximum simulation time. + */ void advance(double t_max = 1.0) { - ScalarType dt_first_mobility = - std::accumulate(this->m_graph.edges().begin(), this->m_graph.edges().end(), - std::numeric_limits::max(), [&](ScalarType current_min, const auto& e) { - auto traveltime_per_region = - round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); - if (traveltime_per_region < 0.01) - traveltime_per_region = 0.01; - auto start_mobility = - round_nth_decimal(1 - 2 * (traveltime_per_region * e.property.path.size()) - - this->m_graph.nodes()[e.end_node_idx].property.stay_duration, - 2); - if (start_mobility < 0) { - start_mobility = 0.; - } - return std::min(current_min, start_mobility); - }); - - // set population to zero in mobility nodes before starting + // Compute earliest mobility start time among all edges. + ScalarType dt_first_mobility = std::accumulate( + this->m_graph.edges().begin(), this->m_graph.edges().end(), std::numeric_limits::max(), + [&](ScalarType current_min, const auto& e) { + auto travel_time_per_region = round_nth_decimal(e.property.travel_time / e.property.path.size(), 2); + travel_time_per_region = (travel_time_per_region < 0.01) ? 0.01 : travel_time_per_region; + auto start_mobility = + round_nth_decimal(1 - 2 * (travel_time_per_region * e.property.path.size()) - + this->m_graph.nodes()[e.end_node_idx].property.stay_duration, + 2); + if (start_mobility < 0.) + start_mobility = 0.; + return std::min(current_min, start_mobility); + }); + + // Reset mobility simulation results before starting. for (auto& n : this->m_graph.nodes()) { n.property.mobility_sim.get_result().get_last_value().setZero(); } - auto min_dt = 0.01; - double t_begin = this->m_t - 1.; + const auto min_dt = 0.01; + double t_begin = this->m_t - 1.0; + // Main simulation loop. while (this->m_t < t_max - epsilon) { t_begin += 1; if (this->m_t + dt_first_mobility > t_max) { @@ -763,46 +802,41 @@ class GraphSimulationExtended break; } - size_t indx_schedule = 0; + size_t index_schedule = 0; while (t_begin + 1 > this->m_t + 1e-10) { - // the graph simulation is structured in 3 steps: - // 1. move indivudals if necessary - // 2. integrate the local nodes - // 3. update the status of the commuter and use the obtained values to update the mobility models. - move_commuters(indx_schedule); - advance_local_nodes(indx_schedule); - update_status_commuters(indx_schedule); - - // in the last time step we have to move the individuals back to their local model and - // delete the commuters time series - handle_last_time_step(indx_schedule); - - indx_schedule++; + // Step 1: Move commuters. + move_commuters(index_schedule); + // Step 2: Advance local nodes. + advance_local_nodes(index_schedule); + // Step 3: Update commuter statuses. + update_status_commuters(index_schedule); + + // Handle last timestep actions. + handle_last_time_step(index_schedule); + + index_schedule++; this->m_t += min_dt; } - // At the end of the day. we set each compartment zero for all mobility nodes since we have to estimate - // the state of the indivuals moving between different counties. - // Therefore there can be differences with the states given by the integrator used for the mobility node. + // At day's end, reset compartments for mobility nodes. for (auto& n : this->m_graph.nodes()) { n.property.mobility_sim.get_result().get_last_value().setZero(); } - - // to save memory, we interpolate each time series after every day if t > 20 + // For simulations beyond t > 20, interpolate results to save memory. if (this->m_t > 20) { for (auto& n : this->m_graph.nodes()) { - // base sim - auto& result_node_local = n.property.base_sim.get_result(); - auto interpolated_result = interpolate_simulation_result(result_node_local); - auto& flow_node_local = n.property.base_sim.get_flows(); - auto interpolated_flows = interpolate_simulation_result(flow_node_local); + // Local simulation interpolation. + auto& result_local = n.property.base_sim.get_result(); + auto interpolated_result = interpolate_simulation_result(result_local); + auto& flows_local = n.property.base_sim.get_flows(); + auto interpolated_flows = interpolate_simulation_result(flows_local); n.property.base_sim.get_result() = interpolated_result; n.property.base_sim.get_flows() = interpolated_flows; - // mobility sim - auto& result_node_mobility = n.property.mobility_sim.get_result(); - interpolated_result = interpolate_simulation_result(result_node_mobility); - auto& flow_node_mobility = n.property.mobility_sim.get_flows(); - interpolated_flows = interpolate_simulation_result(flow_node_mobility); + // Mobility simulation interpolation. + auto& result_mob = n.property.mobility_sim.get_result(); + interpolated_result = interpolate_simulation_result(result_mob); + auto& flows_mob = n.property.mobility_sim.get_flows(); + interpolated_flows = interpolate_simulation_result(flows_mob); n.property.mobility_sim.get_result() = interpolated_result; n.property.mobility_sim.get_flows() = interpolated_flows; } @@ -811,73 +845,90 @@ class GraphSimulationExtended } private: - MobilityFunctions m_mobility_functions; - ScheduleManager::Schedule schedules; - const double epsilon = 1e-10; + MobilityFunctions m_mobility_functions; ///< Mobility functions used to update simulation state. + ScheduleManager::Schedule schedules; ///< Precomputed schedules for mobility. + const double epsilon = 1e-10; ///< Tolerance - ScalarType calculate_next_dt(size_t edge_indx, size_t indx_schedule) + /** + * @brief Calculates the next time step for an edge given its current schedule index. + * + * @param edge_index Index of the edge. + * @param index_schedule Current schedule index. + * @return ScalarType Next time step value. + * + * @throw runtime_error if the schedule index is not found. + */ + ScalarType calculate_next_dt(size_t edge_index, size_t index_schedule) { - auto current_node_indx = schedules.schedule_edges[edge_indx][indx_schedule]; - bool in_mobility_node = schedules.mobility_schedule_edges[edge_indx][indx_schedule]; + auto current_node_idx = schedules.schedule_edges[edge_index][index_schedule]; + bool in_mobility_node = schedules.mobility_schedule_edges[edge_index][index_schedule]; - // determine dt, which is equal to the last integration/synchronization point in the current node - auto integrator_schedule_row = schedules.local_int_schedule[current_node_indx]; + // Select appropriate integration schedule. + auto integrator_schedule_row = schedules.local_int_schedule[current_node_idx]; if (in_mobility_node) - integrator_schedule_row = schedules.mobility_int_schedule[current_node_indx]; - // search the index of indx_schedule in the integrator schedule - const size_t indx_current = std::distance( + integrator_schedule_row = schedules.mobility_int_schedule[current_node_idx]; + + // Locate the current schedule index. + const size_t index_current = std::distance( integrator_schedule_row.begin(), - std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), indx_schedule)); + std::lower_bound(integrator_schedule_row.begin(), integrator_schedule_row.end(), index_schedule)); - if (integrator_schedule_row[indx_current] != indx_schedule) + if (integrator_schedule_row[index_current] != index_schedule) throw std::runtime_error("Error in schedule."); - ScalarType dt_mobility; - if (indx_schedule == 99 || indx_current == integrator_schedule_row.size() - 1) { - // if we are at the last iteration, we choose the minimal time step - dt_mobility = (100 - indx_schedule) * 0.01; + ScalarType dt_mobility = 0; + if (index_schedule == 99 || index_current == integrator_schedule_row.size() - 1) { + // Use minimal timestep at last iteration. + dt_mobility = (100 - index_schedule) * 0.01; } else { - // else, we calculate the next time step based on the next integration point - dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[indx_current + 1]) - - static_cast(integrator_schedule_row[indx_current])) / + // Compute dt based on the next integration point. + dt_mobility = round_nth_decimal((static_cast(integrator_schedule_row[index_current + 1]) - + static_cast(integrator_schedule_row[index_current])) / 100 + epsilon, 2); } - return dt_mobility; } - void move_commuters(size_t indx_schedule) + /** + * @brief Moves commuters along edges that are adressed in the current schedule. + * + * For each edge with mobility active at the current schedule index, this function either initializes mobility + * or moves commuters to the next node. + * + * @param index_schedule Current schedule index. + */ + void move_commuters(size_t index_schedule) { - for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { - auto& e = this->m_graph.edges()[edge_indx]; - // start mobility by initializing the number of commuters and move to initial mobility model - if (indx_schedule == schedules.first_mobility[edge_indx]) { + for (const auto& edge_index : schedules.edges_mobility[index_schedule]) { + auto& e = this->m_graph.edges()[edge_index]; + // Initialize mobility when reaching the first mobility timestep. + if (index_schedule == schedules.first_mobility[edge_index]) { auto& node_from = - this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]].property.base_sim; + this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule - 1]].property.base_sim; auto& node_to = - this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim; + this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]].property.mobility_sim; m_mobility_functions.init_mobility(this->m_t, e.property, node_from, node_to); } - else if (indx_schedule > schedules.first_mobility[edge_indx]) { - // send the individuals to the next node - if ((schedules.schedule_edges[edge_indx][indx_schedule] != - schedules.schedule_edges[edge_indx][indx_schedule - 1]) || - (schedules.mobility_schedule_edges[edge_indx][indx_schedule] != - schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1])) { + else if (index_schedule > schedules.first_mobility[edge_index]) { + // Move commuters if there is a change in node or mobility flag. + if ((schedules.schedule_edges[edge_index][index_schedule] != + schedules.schedule_edges[edge_index][index_schedule - 1]) || + (schedules.mobility_schedule_edges[edge_index][index_schedule] != + schedules.mobility_schedule_edges[edge_index][index_schedule - 1])) { auto& node_from = - schedules.mobility_schedule_edges[edge_indx][indx_schedule - 1] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + schedules.mobility_schedule_edges[edge_index][index_schedule - 1] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule - 1]] .property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule - 1]] + : this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule - 1]] .property.base_sim; - auto& node_to = schedules.mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + auto& node_to = schedules.mobility_schedule_edges[edge_index][index_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]] .property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]] + : this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]] .property.base_sim; m_mobility_functions.move_migrated(this->m_t, e.property, node_from, node_to); @@ -886,89 +937,111 @@ class GraphSimulationExtended } } - void update_status_commuters(size_t indx_schedule, const double max_num_contacts = 20.) + /** + * @brief Updates the status of commuters for edges active at the current schedule index. + * + * @param index_schedule Current schedule index. + * @param max_num_contacts Maximum allowed number of contacts (default 20.0). + */ + void update_status_commuters(size_t index_schedule, const double max_num_contacts = 20.0) { - for (const auto& edge_indx : schedules.edges_mobility[indx_schedule]) { - auto& e = this->m_graph.edges()[edge_indx]; - auto next_dt = calculate_next_dt(edge_indx, indx_schedule); + for (const auto& edge_index : schedules.edges_mobility[index_schedule]) { + auto& e = this->m_graph.edges()[edge_index]; + auto next_dt = calculate_next_dt(edge_index, index_schedule); auto& node_to = - schedules.mobility_schedule_edges[edge_indx][indx_schedule] - ? this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.mobility_sim - : this->m_graph.nodes()[schedules.schedule_edges[edge_indx][indx_schedule]].property.base_sim; + schedules.mobility_schedule_edges[edge_index][index_schedule] + ? this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]].property.mobility_sim + : this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]].property.base_sim; - // get current contact and scale it but only if mobility model + // Get and copy current contact pattern. auto contact_pattern_curr = node_to.get_model().get_contact_pattern(); auto contacts_copy = contact_pattern_curr; - if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) { + if (schedules.mobility_schedule_edges[edge_index][index_schedule]) { auto& contact_matrix = contact_pattern_curr.get_cont_freq_mat(); Eigen::MatrixXd scaled_matrix = contact_matrix[0].get_baseline().eval() / e.property.travel_time; - // check if there a values greater max_num_contacts in the contact matrix. if higher, set to max_num_contacts - for (auto i = 0; i < scaled_matrix.rows(); ++i) { - for (auto j = 0; j < scaled_matrix.cols(); ++j) { + // Cap the contact frequency at max_num_contacts. + for (int i = 0; i < scaled_matrix.rows(); ++i) { + for (int j = 0; j < scaled_matrix.cols(); ++j) { if (scaled_matrix(i, j) > max_num_contacts) { scaled_matrix(i, j) = max_num_contacts; } } } - contact_matrix[0].get_baseline() = scaled_matrix; node_to.get_model().set_contact_pattern(contact_pattern_curr); } m_mobility_functions.update_commuters(this->m_t, next_dt, e.property, node_to, - schedules.mobility_schedule_edges[edge_indx][indx_schedule]); + schedules.mobility_schedule_edges[edge_index][index_schedule]); - // reset contact pattern after estimating the state of the commuters - if (schedules.mobility_schedule_edges[edge_indx][indx_schedule]) + // Reset the contact pattern after update. + if (schedules.mobility_schedule_edges[edge_index][index_schedule]) node_to.get_model().set_contact_pattern(contacts_copy); } } - void advance_local_nodes(size_t indx_schedule) + /** + * @brief Advances the local node simulations based on their integration schedules. + * + * For each node active in the current schedule, this function computes the next time step dt and advances the simulation. + * It also checks for negative or NaN values in the simulation result. + * + * @param index_schedule Current schedule index. + */ + void advance_local_nodes(size_t index_schedule) { - for (const auto& n_indx : schedules.nodes_mobility[indx_schedule]) { - auto& n = this->m_graph.nodes()[n_indx]; - const size_t indx_current = - std::distance(schedules.local_int_schedule[n_indx].begin(), - std::lower_bound(schedules.local_int_schedule[n_indx].begin(), - schedules.local_int_schedule[n_indx].end(), indx_schedule)); - - const size_t val_next = (indx_current == schedules.local_int_schedule[n_indx].size() - 1) + for (const auto& node_index : schedules.nodes_mobility[index_schedule]) { + auto& n = this->m_graph.nodes()[node_index]; + const size_t index_current = + std::distance(schedules.local_int_schedule[node_index].begin(), + std::lower_bound(schedules.local_int_schedule[node_index].begin(), + schedules.local_int_schedule[node_index].end(), index_schedule)); + + const size_t val_next = (index_current == schedules.local_int_schedule[node_index].size() - 1) ? 100 - : schedules.local_int_schedule[n_indx][indx_current + 1]; + : schedules.local_int_schedule[node_index][index_current + 1]; const ScalarType next_dt = - round_nth_decimal((static_cast(val_next) - indx_schedule) / 100 + epsilon, 2); + round_nth_decimal((static_cast(val_next) - index_schedule) / 100 + epsilon, 2); n.property.base_sim.advance(this->m_t + next_dt); - // check if last value contains negative values or nan values + + // Check for negative or NaN values. if (n.property.base_sim.get_result().get_last_value().minCoeff() < -1e-7 || std::isnan(n.property.base_sim.get_result().get_last_value().sum())) { auto last_value_as_vec = std::vector(n.property.base_sim.get_result().get_last_value().data(), n.property.base_sim.get_result().get_last_value().data() + n.property.base_sim.get_result().get_last_value().size()); - log_error("Negative Value " + std::to_string(n_indx) + " at time " + std::to_string(indx_schedule) + - " in local node detected."); + log_error("Negative value in local node " + std::to_string(node_index) + " at schedule index " + + std::to_string(index_schedule)); } } } - void handle_last_time_step(size_t indx_schedule) + /** + * @brief Handles the last timestep of a simulation day. + * + * When the simulation reaches the final schedule index (assumed to be 99), this function moves + * individuals back to their local simulation models and clears commuter time series. + * + * @param index_schedule Current schedule index. + */ + void handle_last_time_step(size_t index_schedule) { - if (indx_schedule == 99) { - auto edge_index = 0; + if (index_schedule == 99) { + size_t edge_index = 0; for (auto& e : this->m_graph.edges()) { auto& node_from = - this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.mobility_sim; + this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]].property.mobility_sim; auto& node_to = - this->m_graph.nodes()[schedules.schedule_edges[edge_index][indx_schedule]].property.base_sim; + this->m_graph.nodes()[schedules.schedule_edges[edge_index][index_schedule]].property.base_sim; - if (schedules.schedule_edges[edge_index][indx_schedule] != e.start_node_idx) - log_error("Last node is not the start node in edge " + std::to_string(edge_index) + " at time " + - std::to_string(indx_schedule)); - // move the individuals back to the local model + if (schedules.schedule_edges[edge_index][index_schedule] != e.start_node_idx) + log_error("Last node is not the start node in edge " + std::to_string(edge_index) + + " at schedule index " + std::to_string(index_schedule)); + // Move individuals back to the local model. m_mobility_functions.move_migrated(this->m_t + 0.01, e.property, node_from, node_to); m_mobility_functions.delete_migrated(e.property); - edge_index++; + ++edge_index; } } }