From f839b276b429909324f5715639a876b199075eae Mon Sep 17 00:00:00 2001 From: julianlitz Date: Tue, 9 Sep 2025 09:53:23 +0000 Subject: [PATCH 1/3] IO Templates --- cpp/examples/ad_odeint_example.cpp | 8 +- cpp/examples/ad_square_example.cpp | 2 +- cpp/examples/ode_seair.cpp | 2 +- cpp/examples/ode_seair_optimization.cpp | 14 +- cpp/memilio/CMakeLists.txt | 1 + cpp/memilio/ad/ad.h | 92 +++ cpp/memilio/ad/include/ad/ad.hpp | 1 - cpp/memilio/io/mobility_io.cpp | 242 +------ cpp/memilio/io/mobility_io.h | 257 ++++++- cpp/memilio/io/parameters_io.cpp | 38 - cpp/memilio/io/parameters_io.h | 48 +- cpp/memilio/io/result_io.cpp | 192 +----- cpp/memilio/io/result_io.h | 310 +++++++-- cpp/memilio/utils/logging.h | 2 +- cpp/models/ode_secir/analyze_result.h | 125 ++-- cpp/models/ode_secir/parameters_io.cpp | 158 ----- cpp/models/ode_secir/parameters_io.h | 330 ++++++--- cpp/models/ode_secirts/analyze_result.cpp | 2 +- cpp/models/ode_secirts/analyze_result.h | 229 +++--- cpp/models/ode_secirts/parameters_io.cpp | 2 +- cpp/models/ode_secirts/parameters_io.h | 213 +++--- cpp/models/ode_secirvvs/analyze_result.cpp | 2 +- cpp/models/ode_secirvvs/analyze_result.h | 239 +++---- cpp/models/ode_secirvvs/parameters_io.cpp | 236 +------ cpp/models/ode_secirvvs/parameters_io.h | 765 +++++++++++++-------- cpp/tests/test_ad.cpp | 2 +- cpp/tests/test_math_floating_point.cpp | 2 +- cpp/tests/test_odesecir.cpp | 183 ++--- cpp/tests/test_odesecirts.cpp | 64 +- cpp/tests/test_odesecirvvs.cpp | 72 +- 30 files changed, 1938 insertions(+), 1895 deletions(-) create mode 100644 cpp/memilio/ad/ad.h diff --git a/cpp/examples/ad_odeint_example.cpp b/cpp/examples/ad_odeint_example.cpp index 03e64fcc72..bcef097afa 100644 --- a/cpp/examples/ad_odeint_example.cpp +++ b/cpp/examples/ad_odeint_example.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include "boost/numeric/odeint.hpp" #include @@ -72,10 +72,10 @@ int main() // We want to compare AD derivatives with difference quotient // To this end, we simulate again with a perturbation of the initial value of x[0] - const double h = 1e-3; // pertubation for finite differences + const double h = 1e-3; // pertubation for finite differences std::array y = {ad::value(x[0]), ad::value(x[1])}; - x[0] = 1.0 + h; // add perturbation to initial value of x[0] - x[1] = 0.0; + x[0] = 1.0 + h; // add perturbation to initial value of x[0] + x[1] = 0.0; // integrate perturbed system boost::numeric::odeint::integrate_adaptive(make_controlled(abs_tol, rel_tol), diff --git a/cpp/examples/ad_square_example.cpp b/cpp/examples/ad_square_example.cpp index 1ba1039fca..68209f00d6 100644 --- a/cpp/examples/ad_square_example.cpp +++ b/cpp/examples/ad_square_example.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include /* This example computes the derivative of f(x) = x^2 in two different ways: diff --git a/cpp/examples/ode_seair.cpp b/cpp/examples/ode_seair.cpp index 330226c524..0d1adfc138 100644 --- a/cpp/examples/ode_seair.cpp +++ b/cpp/examples/ode_seair.cpp @@ -23,7 +23,7 @@ // A detailed description of the model can be found in the publication // Tsay et al. (2020), Modeling, state estimation, and optimal control for the US COVID-19 outbreak. -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include "ode_seair/model.h" #include "ode_seair/infection_state.h" diff --git a/cpp/examples/ode_seair_optimization.cpp b/cpp/examples/ode_seair_optimization.cpp index f86066cd52..78b17cbc97 100644 --- a/cpp/examples/ode_seair_optimization.cpp +++ b/cpp/examples/ode_seair_optimization.cpp @@ -21,7 +21,7 @@ * is extracted from the Ipopt documentation */ -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include "memilio/utils/compiler_diagnostics.h" #include "ode_seair/model.h" @@ -47,13 +47,13 @@ class Seair_NLP : public Ipopt::TNLP { public: - static constexpr double N = 327167434; // total US population - Seair_NLP() = default; - Seair_NLP(const Seair_NLP&) = delete; - Seair_NLP(Seair_NLP&&) = delete; + static constexpr double N = 327167434; // total US population + Seair_NLP() = default; + Seair_NLP(const Seair_NLP&) = delete; + Seair_NLP(Seair_NLP&&) = delete; Seair_NLP& operator=(const Seair_NLP&) = delete; - Seair_NLP& operator=(Seair_NLP&&) = delete; - ~Seair_NLP() = default; + Seair_NLP& operator=(Seair_NLP&&) = delete; + ~Seair_NLP() = default; /** Method to request the initial information about the problem. * diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index 3c8d6b0428..5079672695 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -115,6 +115,7 @@ add_library(memilio utils/string_literal.h utils/type_list.h utils/base_dir.h + ad/ad.h ) target_include_directories(memilio PUBLIC diff --git a/cpp/memilio/ad/ad.h b/cpp/memilio/ad/ad.h new file mode 100644 index 0000000000..daa9e4a421 --- /dev/null +++ b/cpp/memilio/ad/ad.h @@ -0,0 +1,92 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Julian Litz +* +* 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 MIO_AD_H +#define MIO_AD_H + +#include "ad/ad.hpp" +#include "memilio/math/eigen.h" + +#include +#include + +// Extend automatic differentiation (AD) library to support std::round. +namespace ad +{ +namespace internal +{ +using std::round; +template +static inline double round(const ad::internal::active_type& x) +{ + return round(x._value()); +} +template +static inline double round(const ad::internal::binary_intermediate_aa& x) +{ + return round(x._value()); +} +template +static inline double round(const ad::internal::binary_intermediate_ap& x) +{ + return round(x._value()); +} +template +static inline double round(const ad::internal::binary_intermediate_pa& x) +{ + return round(x._value()); +} +template +static inline double round(const ad::internal::unary_intermediate& x) +{ + return round(x._value()); +} +} // namespace internal +} // namespace ad + +// Allow std::numeric_limits to work with AD types. +template +struct std::numeric_limits> : public numeric_limits { +}; + +// Ensures that Eigen recognizes your AD types as valid scalars. +namespace Eigen +{ +template +struct NumTraits> + : GenericNumTraits> { + using Scalar = ad::internal::active_type; + using Real = Scalar; + using NonInteger = Scalar; + using Nested = Scalar; + enum + { + IsComplex = 0, + IsInteger = 0, + IsSigned = 1, + RequireInitialization = 1, + ReadCost = 1, + AddCost = 3, + MulCost = 3 + }; +}; +} // namespace Eigen + +#endif // MIO_AD_H diff --git a/cpp/memilio/ad/include/ad/ad.hpp b/cpp/memilio/ad/include/ad/ad.hpp index 1492151261..c63248f944 100644 --- a/cpp/memilio/ad/include/ad/ad.hpp +++ b/cpp/memilio/ad/include/ad/ad.hpp @@ -4162,4 +4162,3 @@ namespace ad { } #endif - diff --git a/cpp/memilio/io/mobility_io.cpp b/cpp/memilio/io/mobility_io.cpp index 5786aa949b..4bb5247465 100644 --- a/cpp/memilio/io/mobility_io.cpp +++ b/cpp/memilio/io/mobility_io.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Daniel Abele, Wadim Koslow, Henrik Zunker, Martin J. Kuehn @@ -60,248 +60,8 @@ IOResult count_lines(const std::string& filename) return success(count); } -IOResult read_mobility_formatted(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::MatrixXd txt_matrix(num_lines - 1, 3); - std::vector ids; - - try { - std::string tp; - int linenumber = 0; - while (getline(file, tp)) { - if (linenumber > 0) { - auto line = split(tp, '\t'); - if (line.size() < 5) { - return failure(StatusCode::InvalidFileFormat, - filename + ":" + std::to_string(linenumber) + ": Not enough entries in line."); - } - ids.push_back(std::stoi(line[2])); - txt_matrix(linenumber - 1, 0) = std::stoi(line[2]); - txt_matrix(linenumber - 1, 1) = std::stoi(line[3]); - txt_matrix(linenumber - 1, 2) = std::stod(line[4]); - } - linenumber++; - } - } - catch (std::runtime_error& ex) { - return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); - } - - sort(ids.begin(), ids.end()); - std::vector::iterator iter = std::unique(ids.begin(), ids.end()); - ids.resize(std::distance(ids.begin(), iter)); - - Eigen::MatrixXd mobility = Eigen::MatrixXd::Zero(ids.size(), ids.size()); - - for (int k = 0; k < num_lines - 1; k++) { - int row_ind = 0; - int col_ind = 0; - while (txt_matrix(k, 0) != ids[row_ind]) { - row_ind++; - } - while (txt_matrix(k, 1) != ids[col_ind]) { - col_ind++; - } - mobility(row_ind, col_ind) = txt_matrix(k, 2); - } - - return success(mobility); -} - -IOResult read_mobility_plain(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::MatrixXd mobility(num_lines, num_lines); - - try { - std::string tp; - int linenumber = 0; - while (getline(file, tp)) { - auto line = split(tp, ' '); - if (line.size() != size_t(num_lines)) { - return failure(StatusCode::InvalidFileFormat, filename + ": Not a square matrix."); - } - Eigen::Index i = static_cast(linenumber); - for (Eigen::Index j = 0; j < static_cast(line.size()); j++) { - mobility(i, j) = std::stod(line[j]); - } - linenumber++; - } - } - catch (std::runtime_error& ex) { - return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); - } - - return success(mobility); -} - #ifdef MEMILIO_HAS_HDF5 -IOResult save_edges(const std::vector>>& ensemble_edges, - const std::vector>& pairs_edges, const fs::path& result_dir, - bool save_single_runs, bool save_percentiles) -{ - //save results and sum of results over nodes - auto ensemble_edges_sum = sum_nodes(ensemble_edges); - if (save_single_runs) { - for (size_t i = 0; i < ensemble_edges_sum.size(); ++i) { - BOOST_OUTCOME_TRY(save_edges(ensemble_edges[i], pairs_edges, - (result_dir / ("Edges_run" + std::to_string(i) + ".h5")).string())); - } - } - - if (save_percentiles) { - // make directories for percentiles - auto result_dir_p05 = result_dir / "p05"; - auto result_dir_p25 = result_dir / "p25"; - auto result_dir_p50 = result_dir / "p50"; - auto result_dir_p75 = result_dir / "p75"; - auto result_dir_p95 = result_dir / "p95"; - BOOST_OUTCOME_TRY(create_directory(result_dir_p05.string())); - BOOST_OUTCOME_TRY(create_directory(result_dir_p25.string())); - BOOST_OUTCOME_TRY(create_directory(result_dir_p50.string())); - BOOST_OUTCOME_TRY(create_directory(result_dir_p75.string())); - BOOST_OUTCOME_TRY(create_directory(result_dir_p95.string())); - - // save percentiles of Edges - { - auto ensemble_edges_p05 = ensemble_percentile(ensemble_edges, 0.05); - auto ensemble_edges_p25 = ensemble_percentile(ensemble_edges, 0.25); - auto ensemble_edges_p50 = ensemble_percentile(ensemble_edges, 0.50); - auto ensemble_edges_p75 = ensemble_percentile(ensemble_edges, 0.75); - auto ensemble_edges_p95 = ensemble_percentile(ensemble_edges, 0.95); - - BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p05, pairs_edges, (result_dir_p05 / "Edges.h5").string())); - BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p25, pairs_edges, (result_dir_p25 / "Edges.h5").string())); - BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p50, pairs_edges, (result_dir_p50 / "Edges.h5").string())); - BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p75, pairs_edges, (result_dir_p75 / "Edges.h5").string())); - BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p95, pairs_edges, (result_dir_p95 / "Edges.h5").string())); - } - } - return success(); -} -IOResult save_edges(const std::vector>& results, const std::vector>& ids, - const std::string& filename) -{ - const auto num_edges = results.size(); - size_t edge_indx = 0; - // H5Fcreate creates a new HDF5 file. - // H5F_ACC_TRUNC: If the file already exists, H5Fcreate fails. If the file does not exist, it is created and opened with read-write access. - // H5P_DEFAULT: default data transfer properties are used. - H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, "Failed to open the HDF5 file: " + filename); - - while (edge_indx < num_edges) { - const auto& result = results[edge_indx]; - auto h5group_name = "/" + std::to_string(ids[edge_indx].first); - H5Group start_node_h5group{H5Gcreate(file.id, h5group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(start_node_h5group.id, StatusCode::UnknownError, - "Group could not be created (" + h5group_name + ") in the file: " + filename); - - const auto num_timepoints = result.get_num_time_points(); - if (num_timepoints > 0) { - const auto num_elements = result.get_value(0).size(); - - hsize_t dims_t[] = {static_cast(num_timepoints)}; - H5DataSpace dspace_t{H5Screate_simple(1, dims_t, NULL)}; - MEMILIO_H5_CHECK(dspace_t.id, StatusCode::UnknownError, - "Failed to create the DataSpace for 'Time' in group " + h5group_name + - " in the file: " + filename); - - H5DataSet dset_t{H5Dcreate(start_node_h5group.id, "Time", H5T_NATIVE_DOUBLE, dspace_t.id, H5P_DEFAULT, - H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dset_t.id, StatusCode::UnknownError, - "Failed to create the 'Time' DataSet in group " + h5group_name + - " in the file: " + filename); - - auto values_t = std::vector(result.get_times().begin(), result.get_times().end()); - MEMILIO_H5_CHECK(H5Dwrite(dset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, values_t.data()), - StatusCode::UnknownError, - "Failed to write 'Time' data in group " + h5group_name + " in the file: " + filename); - - int start_id = ids[edge_indx].first; - auto total = Eigen::Matrix::Zero(num_timepoints, - num_elements) - .eval(); - while (edge_indx < num_edges && ids[edge_indx].first == start_id) { - const auto& result_edge = results[edge_indx]; - auto edge_result = Eigen::Matrix::Zero( - num_timepoints, num_elements) - .eval(); - for (Eigen::Index t_idx = 0; t_idx < result_edge.get_num_time_points(); ++t_idx) { - auto v = result_edge.get_value(t_idx).transpose().eval(); - edge_result.row(t_idx) = v; - total.row(t_idx) += v; - } - - hsize_t dims_values[] = {static_cast(num_timepoints), static_cast(num_elements)}; - H5DataSpace dspace_values{H5Screate_simple(2, dims_values, NULL)}; - MEMILIO_H5_CHECK(dspace_values.id, StatusCode::UnknownError, - "Failed to create the DataSpace for End" + std::to_string(ids[edge_indx].second) + - " in group " + h5group_name + " in the file: " + filename); - - // End is the target node of the edge - auto dset_name = "End" + std::to_string(ids[edge_indx].second); - H5DataSet dset_values{H5Dcreate(start_node_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, - dspace_values.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dset_values.id, StatusCode::UnknownError, - "Failed to create the DataSet for " + dset_name + " in group " + h5group_name + - " in the file: " + filename); - - MEMILIO_H5_CHECK( - H5Dwrite(dset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, edge_result.data()), - StatusCode::UnknownError, - "Failed to write data for " + dset_name + " in group " + h5group_name + - " in the file: " + filename); - - // In the final iteration, we also save the total values - if (edge_indx == num_edges - 1 || ids[edge_indx + 1].first != start_id) { - dset_name = "Total"; - H5DataSet dset_total{H5Dcreate(start_node_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, - dspace_values.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dset_total.id, StatusCode::UnknownError, - "Failed to create the Total DataSet in group " + h5group_name + - " in the file: " + filename); - - MEMILIO_H5_CHECK( - H5Dwrite(dset_total.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, total.data()), - StatusCode::UnknownError, - "Failed to write Total data in group " + h5group_name + " in the file: " + filename); - } - edge_indx++; - } - } - else { - log_error("No time points in the TimeSeries for Edge combination " + std::to_string(ids[edge_indx].first) + - " -> " + std::to_string(ids[edge_indx].second)); - return failure(mio::StatusCode::InvalidValue); - } - } - return success(); -} #endif // MEMILIO_HAS_HDF5 } // namespace mio diff --git a/cpp/memilio/io/mobility_io.h b/cpp/memilio/io/mobility_io.h index e7ebb4f045..5134b34264 100644 --- a/cpp/memilio/io/mobility_io.h +++ b/cpp/memilio/io/mobility_io.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Daniel Abele, Wadim Koslow, Henrik Zunker, Martin J. Kuehn @@ -25,6 +25,8 @@ #include "memilio/data/analyze_result.h" #include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/io/hdf5_cpp.h" + namespace mio { @@ -44,19 +46,115 @@ IOResult count_lines(const std::string& filename); /** * @brief Reads formatted mobility or contact data which is given in columns * from_str to_str from_rs to_rs count_abs - * and separated by tabs. Writes it into a NxN Eigen Matrix, + * and separated by tabs. Writes it into a NxN Eigen Matrix, * where N is the number of regions * @param filename name of file to be read */ -IOResult read_mobility_formatted(const std::string& filename); +template +IOResult> read_mobility_formatted(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::MatrixX txt_matrix(num_lines - 1, 3); + std::vector ids; + + try { + std::string tp; + int linenumber = 0; + while (getline(file, tp)) { + if (linenumber > 0) { + auto line = split(tp, '\t'); + if (line.size() < 5) { + return failure(StatusCode::InvalidFileFormat, + filename + ":" + std::to_string(linenumber) + ": Not enough entries in line."); + } + ids.push_back(std::stoi(line[2])); + txt_matrix(linenumber - 1, 0) = static_cast(std::stoi(line[2])); + txt_matrix(linenumber - 1, 1) = static_cast(std::stoi(line[3])); + txt_matrix(linenumber - 1, 2) = static_cast(std::stod(line[4])); + } + linenumber++; + } + } + catch (std::runtime_error& ex) { + return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); + } + + sort(ids.begin(), ids.end()); + std::vector::iterator iter = std::unique(ids.begin(), ids.end()); + ids.resize(std::distance(ids.begin(), iter)); + + Eigen::MatrixX mobility = Eigen::MatrixX::Zero(ids.size(), ids.size()); + + for (int k = 0; k < num_lines - 1; k++) { + int row_ind = 0; + int col_ind = 0; + while (txt_matrix(k, 0) != ids[row_ind]) { + row_ind++; + } + while (txt_matrix(k, 1) != ids[col_ind]) { + col_ind++; + } + mobility(row_ind, col_ind) = txt_matrix(k, 2); + } + + return success(mobility); +} /** * @brief Reads txt mobility data or contact which is given by values only - * and separated by spaces. Writes it into a NxN Eigen + * and separated by spaces. Writes it into a NxN Eigen * Matrix, where N is the number of regions * @param filename name of file to be read */ -IOResult read_mobility_plain(const std::string& filename); +template +IOResult> read_mobility_plain(const std::string& filename) +{ + BOOST_OUTCOME_TRY(auto&& num_lines, count_lines(filename)); + + if (num_lines == 0) { + return success(Eigen::MatrixX(0, 0)); + } + + std::fstream file; + file.open(filename, std::ios::in); + if (!file.is_open()) { + return failure(StatusCode::FileNotFound, filename); + } + + Eigen::MatrixX mobility(num_lines, num_lines); + + try { + std::string tp; + int linenumber = 0; + while (getline(file, tp)) { + auto line = split(tp, ' '); + if (line.size() != size_t(num_lines)) { + return failure(StatusCode::InvalidFileFormat, filename + ": Not a square matrix."); + } + Eigen::Index i = static_cast(linenumber); + for (Eigen::Index j = 0; j < static_cast(line.size()); j++) { + mobility(i, j) = static_cast(std::stod(line[j])); + } + linenumber++; + } + } + catch (std::runtime_error& ex) { + return failure(StatusCode::InvalidFileFormat, filename + ": " + ex.what()); + } + + return success(mobility); +} #ifdef MEMILIO_HAS_JSONCPP @@ -192,10 +290,108 @@ IOResult>> read_graph(const std::string& dir * @param[in] results Simulation results per edge of the graph. * @param[in] ids Identifiers for the start and end node of the edges. * @param[in] filename Name of the file where the results will be saved. - * @return Any io errors that occur during writing of the files. + * @return Any io errors that occur during writing of the files. */ -IOResult save_edges(const std::vector>& results, const std::vector>& ids, - const std::string& filename); +template +IOResult save_edges(const std::vector>& results, const std::vector>& ids, + const std::string& filename) +{ + const auto num_edges = results.size(); + size_t edge_indx = 0; + // H5Fcreate creates a new HDF5 file. + // H5F_ACC_TRUNC: If the file already exists, H5Fcreate fails. If the file does not exist, it is created and opened with read-write access. + // H5P_DEFAULT: default data transfer properties are used. + H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, "Failed to open the HDF5 file: " + filename); + + while (edge_indx < num_edges) { + const auto& result = results[edge_indx]; + auto h5group_name = "/" + std::to_string(ids[edge_indx].first); + H5Group start_node_h5group{H5Gcreate(file.id, h5group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(start_node_h5group.id, StatusCode::UnknownError, + "Group could not be created (" + h5group_name + ") in the file: " + filename); + + const auto num_timepoints = result.get_num_time_points(); + if (num_timepoints > 0) { + const auto num_elements = result.get_value(0).size(); + + hsize_t dims_t[] = {static_cast(num_timepoints)}; + H5DataSpace dspace_t{H5Screate_simple(1, dims_t, NULL)}; + MEMILIO_H5_CHECK(dspace_t.id, StatusCode::UnknownError, + "Failed to create the DataSpace for 'Time' in group " + h5group_name + + " in the file: " + filename); + + H5DataSet dset_t{H5Dcreate(start_node_h5group.id, "Time", H5T_NATIVE_DOUBLE, dspace_t.id, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_t.id, StatusCode::UnknownError, + "Failed to create the 'Time' DataSet in group " + h5group_name + + " in the file: " + filename); + + auto values_t = std::vector(result.get_times().begin(), result.get_times().end()); + MEMILIO_H5_CHECK(H5Dwrite(dset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, values_t.data()), + StatusCode::UnknownError, + "Failed to write 'Time' data in group " + h5group_name + " in the file: " + filename); + + int start_id = ids[edge_indx].first; + auto total = + Eigen::Matrix::Zero(num_timepoints, num_elements) + .eval(); + while (edge_indx < num_edges && ids[edge_indx].first == start_id) { + const auto& result_edge = results[edge_indx]; + auto edge_result = Eigen::Matrix::Zero( + num_timepoints, num_elements) + .eval(); + for (Eigen::Index t_idx = 0; t_idx < result_edge.get_num_time_points(); ++t_idx) { + auto v = result_edge.get_value(t_idx).transpose().eval(); + edge_result.row(t_idx) = v; + total.row(t_idx) += v; + } + + hsize_t dims_values[] = {static_cast(num_timepoints), static_cast(num_elements)}; + H5DataSpace dspace_values{H5Screate_simple(2, dims_values, NULL)}; + MEMILIO_H5_CHECK(dspace_values.id, StatusCode::UnknownError, + "Failed to create the DataSpace for End" + std::to_string(ids[edge_indx].second) + + " in group " + h5group_name + " in the file: " + filename); + + // End is the target node of the edge + auto dset_name = "End" + std::to_string(ids[edge_indx].second); + H5DataSet dset_values{H5Dcreate(start_node_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, + dspace_values.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_values.id, StatusCode::UnknownError, + "Failed to create the DataSet for " + dset_name + " in group " + h5group_name + + " in the file: " + filename); + + MEMILIO_H5_CHECK( + H5Dwrite(dset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, edge_result.data()), + StatusCode::UnknownError, + "Failed to write data for " + dset_name + " in group " + h5group_name + + " in the file: " + filename); + + // In the final iteration, we also save the total values + if (edge_indx == num_edges - 1 || ids[edge_indx + 1].first != start_id) { + dset_name = "Total"; + H5DataSet dset_total{H5Dcreate(start_node_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, + dspace_values.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_total.id, StatusCode::UnknownError, + "Failed to create the Total DataSet in group " + h5group_name + + " in the file: " + filename); + + MEMILIO_H5_CHECK( + H5Dwrite(dset_total.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, total.data()), + StatusCode::UnknownError, + "Failed to write Total data in group " + h5group_name + " in the file: " + filename); + } + edge_indx++; + } + } + else { + log_error("No time points in the TimeSeries for Edge combination " + std::to_string(ids[edge_indx].first) + + " -> " + std::to_string(ids[edge_indx].second)); + return failure(mio::StatusCode::InvalidValue); + } + } + return success(); +} /** * @brief Saves the results of a simulation for each edge in the graph. @@ -206,9 +402,50 @@ IOResult save_edges(const std::vector>& results, const * @param[in] save_percentiles [Default: true] Defines if percentiles are written. * @return Any io errors that occur during writing of the files. */ -IOResult save_edges(const std::vector>>& ensemble_edges, +template +IOResult save_edges(const std::vector>>& ensemble_edges, const std::vector>& pairs_edges, const fs::path& result_dir, - bool save_single_runs = true, bool save_percentiles = true); + bool save_single_runs = true, bool save_percentiles = true) +{ + //save results and sum of results over nodes + auto ensemble_edges_sum = sum_nodes(ensemble_edges); + if (save_single_runs) { + for (size_t i = 0; i < ensemble_edges_sum.size(); ++i) { + BOOST_OUTCOME_TRY(save_edges(ensemble_edges[i], pairs_edges, + (result_dir / ("Edges_run" + std::to_string(i) + ".h5")).string())); + } + } + + if (save_percentiles) { + // make directories for percentiles + auto result_dir_p05 = result_dir / "p05"; + auto result_dir_p25 = result_dir / "p25"; + auto result_dir_p50 = result_dir / "p50"; + auto result_dir_p75 = result_dir / "p75"; + auto result_dir_p95 = result_dir / "p95"; + BOOST_OUTCOME_TRY(create_directory(result_dir_p05.string())); + BOOST_OUTCOME_TRY(create_directory(result_dir_p25.string())); + BOOST_OUTCOME_TRY(create_directory(result_dir_p50.string())); + BOOST_OUTCOME_TRY(create_directory(result_dir_p75.string())); + BOOST_OUTCOME_TRY(create_directory(result_dir_p95.string())); + + // save percentiles of Edges + { + auto ensemble_edges_p05 = ensemble_percentile(ensemble_edges, 0.05); + auto ensemble_edges_p25 = ensemble_percentile(ensemble_edges, 0.25); + auto ensemble_edges_p50 = ensemble_percentile(ensemble_edges, 0.50); + auto ensemble_edges_p75 = ensemble_percentile(ensemble_edges, 0.75); + auto ensemble_edges_p95 = ensemble_percentile(ensemble_edges, 0.95); + + BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p05, pairs_edges, (result_dir_p05 / "Edges.h5").string())); + BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p25, pairs_edges, (result_dir_p25 / "Edges.h5").string())); + BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p50, pairs_edges, (result_dir_p50 / "Edges.h5").string())); + BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p75, pairs_edges, (result_dir_p75 / "Edges.h5").string())); + BOOST_OUTCOME_TRY(save_edges(ensemble_edges_p95, pairs_edges, (result_dir_p95 / "Edges.h5").string())); + } + } + return success(); +} #endif //MEMILIO_HAS_HDF5 diff --git a/cpp/memilio/io/parameters_io.cpp b/cpp/memilio/io/parameters_io.cpp index f9f9f8b3c0..07db132e5a 100644 --- a/cpp/memilio/io/parameters_io.cpp +++ b/cpp/memilio/io/parameters_io.cpp @@ -30,43 +30,5 @@ namespace mio { -IOResult>> read_population_data(const std::vector& population_data, - const std::vector& vregion) -{ - std::vector> vnum_population( - vregion.size(), std::vector(ConfirmedCasesDataEntry::age_group_names.size(), 0.0)); - - for (auto&& county_entry : population_data) { - //accumulate population of states or country from population of counties - if (!county_entry.county_id && !county_entry.district_id) { - return failure(StatusCode::InvalidFileFormat, "File with county population expected."); - } - //find region that this county belongs to - //all counties belong to the country (id = 0) - auto it = std::find_if(vregion.begin(), vregion.end(), [&county_entry](auto r) { - return r == 0 || - (county_entry.county_id && - regions::StateId(r) == regions::get_state_id(int(*county_entry.county_id))) || - (county_entry.county_id && regions::CountyId(r) == *county_entry.county_id) || - (county_entry.district_id && regions::DistrictId(r) == *county_entry.district_id); - }); - if (it != vregion.end()) { - auto region_idx = size_t(it - vregion.begin()); - auto& num_population = vnum_population[region_idx]; - for (size_t age = 0; age < num_population.size(); age++) { - num_population[age] += county_entry.population[AgeGroup(age)]; - } - } - } - - return success(vnum_population); -} - -IOResult>> read_population_data(const std::string& path, - const std::vector& vregion) -{ - BOOST_OUTCOME_TRY(auto&& population_data, mio::read_population_data(path)); - return read_population_data(population_data, vregion); -} } // namespace mio #endif //MEMILIO_HAS_JSONCPP diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h index 3ea00fc73b..393ed7c845 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -63,7 +63,7 @@ int get_region_id(const EpiDataEntry& data_entry) * * @return An IOResult indicating success or failure. */ -template +template IOResult compute_divi_data(const std::vector& divi_data, const std::vector& vregion, Date date, std::vector& vnum_icu) { @@ -106,12 +106,12 @@ IOResult compute_divi_data(const std::vector& divi_data, const * * @return An IOResult indicating success or failure. */ -template +template IOResult read_divi_data(const std::string& path, const std::vector& vregion, Date date, std::vector& vnum_icu) { BOOST_OUTCOME_TRY(auto&& divi_data, mio::read_divi_data(path)); - return compute_divi_data(divi_data, vregion, date, vnum_icu); + return compute_divi_data(divi_data, vregion, date, vnum_icu); } /** @@ -122,8 +122,38 @@ IOResult read_divi_data(const std::string& path, const std::vector& v * @return An IOResult containing a vector of vectors, where each inner vector represents the population * distribution across age groups for a specific region, or an error if the function fails. */ -IOResult>> read_population_data(const std::vector& population_data, - const std::vector& vregion); +template +IOResult>> read_population_data(const std::vector& population_data, + const std::vector& vregion) +{ + std::vector> vnum_population(vregion.size(), + std::vector(ConfirmedCasesDataEntry::age_group_names.size(), 0.0)); + + for (auto&& county_entry : population_data) { + //accumulate population of states or country from population of counties + if (!county_entry.county_id && !county_entry.district_id) { + return failure(StatusCode::InvalidFileFormat, "File with county population expected."); + } + //find region that this county belongs to + //all counties belong to the country (id = 0) + auto it = std::find_if(vregion.begin(), vregion.end(), [&county_entry](auto r) { + return r == 0 || + (county_entry.county_id && + regions::StateId(r) == regions::get_state_id(int(*county_entry.county_id))) || + (county_entry.county_id && regions::CountyId(r) == *county_entry.county_id) || + (county_entry.district_id && regions::DistrictId(r) == *county_entry.district_id); + }); + if (it != vregion.end()) { + auto region_idx = size_t(it - vregion.begin()); + auto& num_population = vnum_population[region_idx]; + for (size_t age = 0; age < num_population.size(); age++) { + num_population[age] += county_entry.population[AgeGroup(age)]; + } + } + } + + return success(vnum_population); +} /** * @brief Reads population data from census data. @@ -133,8 +163,12 @@ IOResult>> read_population_data(const std::vecto * @return An IOResult containing a vector of vectors, where each inner vector represents the population * distribution across age groups for a specific region, or an error if the function fails. */ -IOResult>> read_population_data(const std::string& path, - const std::vector& vregion); +template +IOResult>> read_population_data(const std::string& path, const std::vector& vregion) +{ + BOOST_OUTCOME_TRY(auto&& population_data, mio::read_population_data(path)); + return read_population_data(population_data, vregion); +} } // namespace mio diff --git a/cpp/memilio/io/result_io.cpp b/cpp/memilio/io/result_io.cpp index 8bd0fe7592..6f24091f49 100644 --- a/cpp/memilio/io/result_io.cpp +++ b/cpp/memilio/io/result_io.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, Martin J. Kuehn @@ -31,196 +31,6 @@ namespace mio { -IOResult save_result(const std::vector>& results, const std::vector& ids, int num_groups, - const std::string& filename) -{ - int region_idx = 0; - H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, filename); - for (auto& result : results) { - auto h5group_name = "/" + std::to_string(ids[region_idx]); - H5Group region_h5group{H5Gcreate(file.id, h5group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(region_h5group.id, StatusCode::UnknownError, - "Group could not be created (" + h5group_name + ")"); - - const int num_timepoints = static_cast(result.get_num_time_points()); - const int num_infectionstates = (int)result.get_num_elements() / num_groups; - - hsize_t dims_t[] = {static_cast(num_timepoints)}; - H5DataSpace dspace_t{H5Screate_simple(1, dims_t, NULL)}; - MEMILIO_H5_CHECK(dspace_t.id, StatusCode::UnknownError, "Time DataSpace could not be created."); - H5DataSet dset_t{H5Dcreate(region_h5group.id, "Time", H5T_NATIVE_DOUBLE, dspace_t.id, H5P_DEFAULT, H5P_DEFAULT, - H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dset_t.id, StatusCode::UnknownError, "Time DataSet could not be created (Time)."); - auto values_t = std::vector(result.get_times().begin(), result.get_times().end()); - MEMILIO_H5_CHECK(H5Dwrite(dset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, values_t.data()), - StatusCode::UnknownError, "Time data could not be written."); - - auto total = Eigen::Matrix::Zero(num_timepoints, - num_infectionstates) - .eval(); - - for (int group_idx = 0; group_idx <= num_groups; ++group_idx) { - auto group = Eigen::Matrix::Zero( - num_timepoints, num_infectionstates) - .eval(); - if (group_idx < num_groups) { - for (Eigen::Index t_idx = 0; t_idx < result.get_num_time_points(); ++t_idx) { - auto v = result[t_idx].transpose().eval(); - auto group_slice = mio::slice(v, {group_idx * num_infectionstates, num_infectionstates}); - mio::slice(group, {t_idx, 1}, {0, num_infectionstates}) = group_slice; - mio::slice(total, {t_idx, 1}, {0, num_infectionstates}) += group_slice; - } - } - - hsize_t dims_values[] = {static_cast(num_timepoints), static_cast(num_infectionstates)}; - H5DataSpace dspace_values{H5Screate_simple(2, dims_values, NULL)}; - MEMILIO_H5_CHECK(dspace_values.id, StatusCode::UnknownError, "Values DataSpace could not be created."); - auto dset_name = group_idx == num_groups ? std::string("Total") : "Group" + std::to_string(group_idx + 1); - H5DataSet dset_values{H5Dcreate(region_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, dspace_values.id, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dset_values.id, StatusCode::UnknownError, "Values DataSet could not be created."); - - MEMILIO_H5_CHECK(H5Dwrite(dset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, - group_idx == num_groups ? total.data() : group.data()), - StatusCode::UnknownError, "Values data could not be written."); - } - region_idx++; - } - return success(); -} - -herr_t store_group_name(hid_t /*id*/, const char* name, const H5L_info_t* /*linfo*/, void* opdata) -{ - auto h5group_names = reinterpret_cast*>(opdata); - h5group_names->push_back(name); - return 0; -} - -IOResult> read_result(const std::string& filename) -{ - std::vector results; - - H5File file{H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)}; - MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, filename); - - std::vector h5group_names; - MEMILIO_H5_CHECK(H5Literate(file.id, H5_INDEX_NAME, H5_ITER_INC, NULL, &store_group_name, &h5group_names), - StatusCode::UnknownError, "Group names could not be read."); - - for (auto& h5group_name : h5group_names) { - H5Group region_h5group{H5Gopen(file.id, h5group_name.c_str(), H5P_DEFAULT)}; - MEMILIO_H5_CHECK(region_h5group.id, StatusCode::UnknownError, - "Group could not be opened (" + h5group_name + ")"); - - std::vector h5dset_names; - MEMILIO_H5_CHECK( - H5Literate(region_h5group.id, H5_INDEX_NAME, H5_ITER_INC, NULL, &store_group_name, &h5dset_names), - StatusCode::UnknownError, "Dataset names could not be read."); - - std::string h5_key = std::any_of(h5dset_names.begin(), h5dset_names.end(), - [](const std::string& str) { - return str.find("Group") == 0; - }) - ? "Group" - : "End"; - - auto num_groups = (Eigen::Index)std::count_if(h5dset_names.begin(), h5dset_names.end(), [&h5_key](auto&& str) { - return str.find(h5_key) != std::string::npos; - }); - - H5DataSet dataset_t{H5Dopen(region_h5group.id, "Time", H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dataset_t.id, StatusCode::UnknownError, "Time DataSet could not be read."); - - // dataset dimensions - H5DataSpace dataspace_t{H5Dget_space(dataset_t.id)}; - MEMILIO_H5_CHECK(dataspace_t.id, StatusCode::UnknownError, "Time DataSpace could not be read."); - const auto n_dims_t = 1; - hsize_t dims_t[n_dims_t]; - H5Sget_simple_extent_dims(dataspace_t.id, dims_t, NULL); - - auto num_timepoints = Eigen::Index(dims_t[0]); - auto time = std::vector(num_timepoints); - MEMILIO_H5_CHECK(H5Dread(dataset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, time.data()), - StatusCode::UnknownError, "Time data could not be read."); - - auto dataset_name_total("/" + h5group_name + "/Total"); - H5DataSet dataset_total{H5Dopen(file.id, dataset_name_total.c_str(), H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dataset_total.id, StatusCode::UnknownError, "Totals DataSet could not be read."); - - //read data space dimensions - H5DataSpace dataspace_total{H5Dget_space(dataset_total.id)}; - MEMILIO_H5_CHECK(dataspace_total.id, StatusCode::UnknownError, "Totals DataSpace could not be read."); - const auto n_dims_total = 2; - hsize_t dims_total[n_dims_total]; - H5Sget_simple_extent_dims(dataspace_total.id, dims_total, NULL); - if (num_timepoints != Eigen::Index(dims_total[0])) { - return failure(StatusCode::InvalidFileFormat, "Number of time points does not match."); - } - auto num_infectionstates = Eigen::Index(dims_total[1]); - - auto total_values = - Eigen::Matrix(num_timepoints, num_infectionstates); - MEMILIO_H5_CHECK( - H5Dread(dataset_total.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, total_values.data()), - StatusCode::UnknownError, "Totals data could not be read"); - - auto totals = TimeSeries(num_infectionstates); - totals.reserve(num_timepoints); - for (auto t_idx = 0; t_idx < num_timepoints; ++t_idx) { - totals.add_time_point(time[t_idx], slice(total_values, {t_idx, 1}, {0, num_infectionstates}).transpose()); - } - - auto groups = TimeSeries(num_infectionstates * num_groups); - groups.reserve(num_timepoints); - for (Eigen::Index t_idx = 0; t_idx < num_timepoints; ++t_idx) { - groups.add_time_point(time[t_idx]); - } - - std::vector h5_key_indices; - // Extract group indices from h5dset_names - for (const auto& name : h5dset_names) { - if (name.find(h5_key) == 0) { - h5_key_indices.push_back(std::stoi(name.substr(h5_key.size()))); - } - } - - for (auto h5_key_indx = size_t(0); h5_key_indx < h5_key_indices.size(); h5_key_indx++) { - auto group_name = "/" + h5group_name + "/" + h5_key + std::to_string(h5_key_indices[h5_key_indx]); - H5DataSet dataset_values{H5Dopen(file.id, group_name.c_str(), H5P_DEFAULT)}; - MEMILIO_H5_CHECK(dataset_values.id, StatusCode::UnknownError, "Values DataSet could not be read."); - - //read data space dimensions - H5DataSpace dataspace_values{H5Dget_space(dataset_values.id)}; - MEMILIO_H5_CHECK(dataspace_values.id, StatusCode::UnknownError, "Values DataSpace could not be read."); - const auto n_dims_values = 2; - hsize_t dims_values[n_dims_values]; - H5Sget_simple_extent_dims(dataspace_values.id, dims_values, NULL); - if (num_timepoints != Eigen::Index(dims_values[0])) { - return failure(StatusCode::InvalidFileFormat, "Number of time points does not match."); - } - if (num_infectionstates != Eigen::Index(dims_values[1])) { - return failure(StatusCode::InvalidFileFormat, "Number of infection states does not match."); - } - - auto group_values = Eigen::Matrix( - num_timepoints, num_infectionstates); - MEMILIO_H5_CHECK( - H5Dread(dataset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, group_values.data()), - StatusCode::UnknownError, "Values data could not be read"); - - for (Eigen::Index idx_t = 0; idx_t < num_timepoints; idx_t++) { - for (Eigen::Index idx_c = 0; idx_c < num_infectionstates; idx_c++) { - groups[idx_t][num_infectionstates * h5_key_indx + idx_c] = group_values(idx_t, idx_c); - } - } - } - - results.push_back(SimulationResult(groups, totals)); - } - return success(results); -} - } // namespace mio #endif //MEMILIO_HAS_HDF5 diff --git a/cpp/memilio/io/result_io.h b/cpp/memilio/io/result_io.h index a3615b12c5..958a3bd720 100644 --- a/cpp/memilio/io/result_io.h +++ b/cpp/memilio/io/result_io.h @@ -43,9 +43,67 @@ namespace mio * @param filename Name of file * @return Any io errors that occur during writing of the files. */ -IOResult save_result(const std::vector>& result, const std::vector& ids, int num_groups, - const std::string& filename); +template +IOResult save_result(const std::vector>& results, const std::vector& ids, int num_groups, + const std::string& filename) +{ + int region_idx = 0; + H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, filename); + for (auto& result : results) { + auto h5group_name = "/" + std::to_string(ids[region_idx]); + H5Group region_h5group{H5Gcreate(file.id, h5group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(region_h5group.id, StatusCode::UnknownError, + "Group could not be created (" + h5group_name + ")"); + + const int num_timepoints = static_cast(result.get_num_time_points()); + const int num_infectionstates = (int)result.get_num_elements() / num_groups; + + hsize_t dims_t[] = {static_cast(num_timepoints)}; + H5DataSpace dspace_t{H5Screate_simple(1, dims_t, NULL)}; + MEMILIO_H5_CHECK(dspace_t.id, StatusCode::UnknownError, "Time DataSpace could not be created."); + H5DataSet dset_t{H5Dcreate(region_h5group.id, "Time", H5T_NATIVE_DOUBLE, dspace_t.id, H5P_DEFAULT, H5P_DEFAULT, + H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_t.id, StatusCode::UnknownError, "Time DataSet could not be created (Time)."); + auto values_t = std::vector(result.get_times().begin(), result.get_times().end()); + MEMILIO_H5_CHECK(H5Dwrite(dset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, values_t.data()), + StatusCode::UnknownError, "Time data could not be written."); + + auto total = Eigen::Matrix::Zero(num_timepoints, + num_infectionstates) + .eval(); + for (int group_idx = 0; group_idx <= num_groups; ++group_idx) { + auto group = Eigen::Matrix::Zero(num_timepoints, + num_infectionstates) + .eval(); + if (group_idx < num_groups) { + for (Eigen::Index t_idx = 0; t_idx < result.get_num_time_points(); ++t_idx) { + auto v = result[t_idx].transpose().eval(); + auto group_slice = mio::slice(v, {group_idx * num_infectionstates, num_infectionstates}); + mio::slice(group, {t_idx, 1}, {0, num_infectionstates}) = group_slice; + mio::slice(total, {t_idx, 1}, {0, num_infectionstates}) += group_slice; + } + } + + hsize_t dims_values[] = {static_cast(num_timepoints), static_cast(num_infectionstates)}; + H5DataSpace dspace_values{H5Screate_simple(2, dims_values, NULL)}; + MEMILIO_H5_CHECK(dspace_values.id, StatusCode::UnknownError, "Values DataSpace could not be created."); + auto dset_name = group_idx == num_groups ? std::string("Total") : "Group" + std::to_string(group_idx + 1); + H5DataSet dset_values{H5Dcreate(region_h5group.id, dset_name.c_str(), H5T_NATIVE_DOUBLE, dspace_values.id, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_values.id, StatusCode::UnknownError, "Values DataSet could not be created."); + + MEMILIO_H5_CHECK(H5Dwrite(dset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, + group_idx == num_groups ? total.data() : group.data()), + StatusCode::UnknownError, "Values data could not be written."); + } + region_idx++; + } + return success(); +} + +template class SimulationResult { public: @@ -65,7 +123,7 @@ class SimulationResult * @param groups Simulation results of individual groups. * @param total Simulation results as the sum over all groups. */ - SimulationResult(const TimeSeries& groups, const TimeSeries& totals) + SimulationResult(const TimeSeries& groups, const TimeSeries& totals) : m_groups(groups) , m_totals(totals) { @@ -74,7 +132,7 @@ class SimulationResult /** * @brief Simulation results of individual groups. */ - const TimeSeries& get_groups() const + const TimeSeries& get_groups() const { return m_groups; } @@ -82,21 +140,151 @@ class SimulationResult /** * @brief Simulation results of the sum over all groups. */ - const TimeSeries& get_totals() const + const TimeSeries& get_totals() const { return m_totals; } private: - TimeSeries m_groups; - TimeSeries m_totals; + TimeSeries m_groups; + TimeSeries m_totals; }; +inline herr_t store_group_name(hid_t /*id*/, const char* name, const H5L_info_t* /*linfo*/, void* opdata) +{ + auto h5group_names = reinterpret_cast*>(opdata); + h5group_names->push_back(name); + return 0; +} + /** * @brief Read simulation result from h5 file. * @param filename name of the file to be read. */ -IOResult> read_result(const std::string& filename); +template +IOResult>> read_result(const std::string& filename) +{ + std::vector> results; + + H5File file{H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, StatusCode::FileNotFound, filename); + + std::vector h5group_names; + MEMILIO_H5_CHECK(H5Literate(file.id, H5_INDEX_NAME, H5_ITER_INC, NULL, &store_group_name, &h5group_names), + StatusCode::UnknownError, "Group names could not be read."); + + for (auto& h5group_name : h5group_names) { + H5Group region_h5group{H5Gopen(file.id, h5group_name.c_str(), H5P_DEFAULT)}; + MEMILIO_H5_CHECK(region_h5group.id, StatusCode::UnknownError, + "Group could not be opened (" + h5group_name + ")"); + + std::vector h5dset_names; + MEMILIO_H5_CHECK( + H5Literate(region_h5group.id, H5_INDEX_NAME, H5_ITER_INC, NULL, &store_group_name, &h5dset_names), + StatusCode::UnknownError, "Dataset names could not be read."); + + std::string h5_key = std::any_of(h5dset_names.begin(), h5dset_names.end(), + [](const std::string& str) { + return str.find("Group") == 0; + }) + ? "Group" + : "End"; + + auto num_groups = (Eigen::Index)std::count_if(h5dset_names.begin(), h5dset_names.end(), [&h5_key](auto&& str) { + return str.find(h5_key) != std::string::npos; + }); + + H5DataSet dataset_t{H5Dopen(region_h5group.id, "Time", H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dataset_t.id, StatusCode::UnknownError, "Time DataSet could not be read."); + + // dataset dimensions + H5DataSpace dataspace_t{H5Dget_space(dataset_t.id)}; + MEMILIO_H5_CHECK(dataspace_t.id, StatusCode::UnknownError, "Time DataSpace could not be read."); + const auto n_dims_t = 1; + hsize_t dims_t[n_dims_t]; + H5Sget_simple_extent_dims(dataspace_t.id, dims_t, NULL); + + auto num_timepoints = Eigen::Index(dims_t[0]); + auto time = std::vector(num_timepoints); + MEMILIO_H5_CHECK(H5Dread(dataset_t.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, time.data()), + StatusCode::UnknownError, "Time data could not be read."); + + auto dataset_name_total("/" + h5group_name + "/Total"); + H5DataSet dataset_total{H5Dopen(file.id, dataset_name_total.c_str(), H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dataset_total.id, StatusCode::UnknownError, "Totals DataSet could not be read."); + + //read data space dimensions + H5DataSpace dataspace_total{H5Dget_space(dataset_total.id)}; + MEMILIO_H5_CHECK(dataspace_total.id, StatusCode::UnknownError, "Totals DataSpace could not be read."); + const auto n_dims_total = 2; + hsize_t dims_total[n_dims_total]; + H5Sget_simple_extent_dims(dataspace_total.id, dims_total, NULL); + if (num_timepoints != Eigen::Index(dims_total[0])) { + return failure(StatusCode::InvalidFileFormat, "Number of time points does not match."); + } + auto num_infectionstates = Eigen::Index(dims_total[1]); + + auto total_values = + Eigen::Matrix(num_timepoints, num_infectionstates); + MEMILIO_H5_CHECK( + H5Dread(dataset_total.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, total_values.data()), + StatusCode::UnknownError, "Totals data could not be read"); + + auto totals = TimeSeries(num_infectionstates); + totals.reserve(num_timepoints); + for (auto t_idx = 0; t_idx < num_timepoints; ++t_idx) { + totals.add_time_point(time[t_idx], slice(total_values, {t_idx, 1}, {0, num_infectionstates}).transpose()); + } + + auto groups = TimeSeries(num_infectionstates * num_groups); + groups.reserve(num_timepoints); + for (Eigen::Index t_idx = 0; t_idx < num_timepoints; ++t_idx) { + groups.add_time_point(time[t_idx]); + } + + std::vector h5_key_indices; + // Extract group indices from h5dset_names + for (const auto& name : h5dset_names) { + if (name.find(h5_key) == 0) { + h5_key_indices.push_back(std::stoi(name.substr(h5_key.size()))); + } + } + + for (auto h5_key_indx = size_t(0); h5_key_indx < h5_key_indices.size(); h5_key_indx++) { + auto group_name = "/" + h5group_name + "/" + h5_key + std::to_string(h5_key_indices[h5_key_indx]); + H5DataSet dataset_values{H5Dopen(file.id, group_name.c_str(), H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dataset_values.id, StatusCode::UnknownError, "Values DataSet could not be read."); + + //read data space dimensions + H5DataSpace dataspace_values{H5Dget_space(dataset_values.id)}; + MEMILIO_H5_CHECK(dataspace_values.id, StatusCode::UnknownError, "Values DataSpace could not be read."); + const auto n_dims_values = 2; + hsize_t dims_values[n_dims_values]; + H5Sget_simple_extent_dims(dataspace_values.id, dims_values, NULL); + if (num_timepoints != Eigen::Index(dims_values[0])) { + return failure(StatusCode::InvalidFileFormat, "Number of time points does not match."); + } + if (num_infectionstates != Eigen::Index(dims_values[1])) { + return failure(StatusCode::InvalidFileFormat, "Number of infection states does not match."); + } + + auto group_values = + Eigen::Matrix(num_timepoints, num_infectionstates); + MEMILIO_H5_CHECK( + H5Dread(dataset_values.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, group_values.data()), + StatusCode::UnknownError, "Values data could not be read"); + + for (Eigen::Index idx_t = 0; idx_t < num_timepoints; idx_t++) { + for (Eigen::Index idx_c = 0; idx_c < num_infectionstates; idx_c++) { + groups[idx_t][num_infectionstates * h5_key_indx + idx_c] = group_values(idx_t, idx_c); + } + } + } + + results.push_back(SimulationResult(groups, totals)); + } + return success(results); +} /** * Save the results and the parameters of a single graph simulation run. @@ -108,16 +296,16 @@ IOResult> read_result(const std::string& filename) * @param run_idx Index of the run; used in directory name. * @return Any io errors that occur during writing of the files. */ -template -IOResult save_result_with_params(const std::vector>& result, const std::vector& params, +template +IOResult save_result_with_params(const std::vector>& result, const std::vector& params, const std::vector& county_ids, const fs::path& result_dir, size_t run_idx) { auto result_dir_run = result_dir / ("run" + std::to_string(run_idx)); BOOST_OUTCOME_TRY(create_directory(result_dir_run.string())); - BOOST_OUTCOME_TRY(save_result(result, county_ids, (int)(size_t)params[0].parameters.get_num_groups(), - (result_dir_run / "Result.h5").string())); - BOOST_OUTCOME_TRY(write_graph(create_graph_without_edges>(params, county_ids), - result_dir_run.string(), IOF_OmitDistributions)); + BOOST_OUTCOME_TRY(save_result(result, county_ids, (int)(size_t)params[0].parameters.get_num_groups(), + (result_dir_run / "Result.h5").string())); + BOOST_OUTCOME_TRY(write_graph(create_graph_without_edges>(params, county_ids), + result_dir_run.string(), IOF_OmitDistributions)); return success(); } @@ -132,8 +320,8 @@ IOResult save_result_with_params(const std::vector>& re * @param save_single_runs [Default: true] Defines if percentiles are written to the disk. * @return Any io errors that occur during writing of the files. */ -template -IOResult save_results(const std::vector>>& ensemble_results, +template +IOResult save_results(const std::vector>>& ensemble_results, const std::vector>& ensemble_params, const std::vector& county_ids, const fs::path& result_dir, bool save_single_runs = true, bool save_percentiles = true) { @@ -142,10 +330,10 @@ IOResult save_results(const std::vector>>& auto num_groups = (int)(size_t)ensemble_params[0][0].parameters.get_num_groups(); if (save_single_runs) { for (size_t i = 0; i < ensemble_result_sum.size(); ++i) { - BOOST_OUTCOME_TRY(save_result(ensemble_result_sum[i], {0}, num_groups, - (result_dir / ("results_run" + std::to_string(i) + "_sum.h5")).string())); - BOOST_OUTCOME_TRY(save_result(ensemble_results[i], county_ids, num_groups, - (result_dir / ("results_run" + std::to_string(i) + ".h5")).string())); + BOOST_OUTCOME_TRY(save_result(ensemble_result_sum[i], {0}, num_groups, + (result_dir / ("results_run" + std::to_string(i) + "_sum.h5")).string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results[i], county_ids, num_groups, + (result_dir / ("results_run" + std::to_string(i) + ".h5")).string())); } } @@ -164,65 +352,65 @@ IOResult save_results(const std::vector>>& // save percentiles of results, summed over nodes { - auto ensemble_results_sum_p05 = ensemble_percentile(ensemble_result_sum, 0.05); - auto ensemble_results_sum_p25 = ensemble_percentile(ensemble_result_sum, 0.25); - auto ensemble_results_sum_p50 = ensemble_percentile(ensemble_result_sum, 0.50); - auto ensemble_results_sum_p75 = ensemble_percentile(ensemble_result_sum, 0.75); - auto ensemble_results_sum_p95 = ensemble_percentile(ensemble_result_sum, 0.95); + auto ensemble_results_sum_p05 = ensemble_percentile(ensemble_result_sum, 0.05); + auto ensemble_results_sum_p25 = ensemble_percentile(ensemble_result_sum, 0.25); + auto ensemble_results_sum_p50 = ensemble_percentile(ensemble_result_sum, 0.50); + auto ensemble_results_sum_p75 = ensemble_percentile(ensemble_result_sum, 0.75); + auto ensemble_results_sum_p95 = ensemble_percentile(ensemble_result_sum, 0.95); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_sum_p05, {0}, num_groups, (result_dir_p05 / "Results_sum.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_sum_p25, {0}, num_groups, (result_dir_p25 / "Results_sum.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_sum_p50, {0}, num_groups, (result_dir_p50 / "Results_sum.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_sum_p75, {0}, num_groups, (result_dir_p75 / "Results_sum.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_sum_p95, {0}, num_groups, (result_dir_p95 / "Results_sum.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_sum_p05, {0}, num_groups, + (result_dir_p05 / "Results_sum.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_sum_p25, {0}, num_groups, + (result_dir_p25 / "Results_sum.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_sum_p50, {0}, num_groups, + (result_dir_p50 / "Results_sum.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_sum_p75, {0}, num_groups, + (result_dir_p75 / "Results_sum.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_sum_p95, {0}, num_groups, + (result_dir_p95 / "Results_sum.h5").string())); } // save percentiles of results { - auto ensemble_results_p05 = ensemble_percentile(ensemble_results, 0.05); - auto ensemble_results_p25 = ensemble_percentile(ensemble_results, 0.25); - auto ensemble_results_p50 = ensemble_percentile(ensemble_results, 0.50); - auto ensemble_results_p75 = ensemble_percentile(ensemble_results, 0.75); - auto ensemble_results_p95 = ensemble_percentile(ensemble_results, 0.95); + auto ensemble_results_p05 = ensemble_percentile(ensemble_results, 0.05); + auto ensemble_results_p25 = ensemble_percentile(ensemble_results, 0.25); + auto ensemble_results_p50 = ensemble_percentile(ensemble_results, 0.50); + auto ensemble_results_p75 = ensemble_percentile(ensemble_results, 0.75); + auto ensemble_results_p95 = ensemble_percentile(ensemble_results, 0.95); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_p05, county_ids, num_groups, (result_dir_p05 / "Results.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_p25, county_ids, num_groups, (result_dir_p25 / "Results.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_p50, county_ids, num_groups, (result_dir_p50 / "Results.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_p75, county_ids, num_groups, (result_dir_p75 / "Results.h5").string())); - BOOST_OUTCOME_TRY( - save_result(ensemble_results_p95, county_ids, num_groups, (result_dir_p95 / "Results.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_p05, county_ids, num_groups, + (result_dir_p05 / "Results.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_p25, county_ids, num_groups, + (result_dir_p25 / "Results.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_p50, county_ids, num_groups, + (result_dir_p50 / "Results.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_p75, county_ids, num_groups, + (result_dir_p75 / "Results.h5").string())); + BOOST_OUTCOME_TRY(save_result(ensemble_results_p95, county_ids, num_groups, + (result_dir_p95 / "Results.h5").string())); } // save percentiles of parameters { - auto ensemble_params_p05 = ensemble_params_percentile(ensemble_params, 0.05); - auto ensemble_params_p25 = ensemble_params_percentile(ensemble_params, 0.25); - auto ensemble_params_p50 = ensemble_params_percentile(ensemble_params, 0.50); - auto ensemble_params_p75 = ensemble_params_percentile(ensemble_params, 0.75); - auto ensemble_params_p95 = ensemble_params_percentile(ensemble_params, 0.95); + auto ensemble_params_p05 = ensemble_params_percentile(ensemble_params, 0.05); + auto ensemble_params_p25 = ensemble_params_percentile(ensemble_params, 0.25); + auto ensemble_params_p50 = ensemble_params_percentile(ensemble_params, 0.50); + auto ensemble_params_p75 = ensemble_params_percentile(ensemble_params, 0.75); + auto ensemble_params_p95 = ensemble_params_percentile(ensemble_params, 0.95); auto make_graph = [&county_ids](auto&& params) { - return create_graph_without_edges>(params, county_ids); + return create_graph_without_edges>(params, county_ids); }; BOOST_OUTCOME_TRY( - write_graph(make_graph(ensemble_params_p05), result_dir_p05.string(), IOF_OmitDistributions)); + write_graph(make_graph(ensemble_params_p05), result_dir_p05.string(), IOF_OmitDistributions)); BOOST_OUTCOME_TRY( - write_graph(make_graph(ensemble_params_p25), result_dir_p25.string(), IOF_OmitDistributions)); + write_graph(make_graph(ensemble_params_p25), result_dir_p25.string(), IOF_OmitDistributions)); BOOST_OUTCOME_TRY( - write_graph(make_graph(ensemble_params_p50), result_dir_p50.string(), IOF_OmitDistributions)); + write_graph(make_graph(ensemble_params_p50), result_dir_p50.string(), IOF_OmitDistributions)); BOOST_OUTCOME_TRY( - write_graph(make_graph(ensemble_params_p75), result_dir_p75.string(), IOF_OmitDistributions)); + write_graph(make_graph(ensemble_params_p75), result_dir_p75.string(), IOF_OmitDistributions)); BOOST_OUTCOME_TRY( - write_graph(make_graph(ensemble_params_p95), result_dir_p95.string(), IOF_OmitDistributions)); + write_graph(make_graph(ensemble_params_p95), result_dir_p95.string(), IOF_OmitDistributions)); } } return success(); diff --git a/cpp/memilio/utils/logging.h b/cpp/memilio/utils/logging.h index 4ea584d499..d7a011c131 100644 --- a/cpp/memilio/utils/logging.h +++ b/cpp/memilio/utils/logging.h @@ -27,7 +27,7 @@ #endif #include "memilio/utils/compiler_diagnostics.h" -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" // C4996: Some stdext functions used in spdlog 1.11 are marked as deprecated in version 19.38.33135.0 of MSVC. Maybe a future version of spdlog will fix this. MSVC_WARNING_DISABLE_PUSH(4996) diff --git a/cpp/models/ode_secir/analyze_result.h b/cpp/models/ode_secir/analyze_result.h index 717b485e24..4c55c3def3 100644 --- a/cpp/models/ode_secir/analyze_result.h +++ b/cpp/models/ode_secir/analyze_result.h @@ -35,8 +35,8 @@ namespace osecir * @param p percentile value in open interval (0, 1) * @return p percentile of the parameters over all runs */ -template -std::vector ensemble_params_percentile(const std::vector>& ensemble_params, double p) +template +std::vector ensemble_params_percentile(const std::vector>& ensemble_params, FP p) { assert(p > 0.0 && p < 1.0 && "Invalid percentile value."); @@ -44,12 +44,12 @@ std::vector ensemble_params_percentile(const std::vector single_element_ensemble(num_runs); + std::vector single_element_ensemble(num_runs); // lambda function that calculates the percentile of a single parameter std::vector percentile(num_nodes, Model((int)num_groups)); auto param_percentil = [&ensemble_params, p, num_runs, &percentile](auto n, auto get_param) mutable { - std::vector single_element(num_runs); + std::vector single_element(num_runs); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][n]; single_element[run] = get_param(params); @@ -63,81 +63,70 @@ std::vector ensemble_params_percentile(const std::vector auto& { - return model.populations[{i, (InfectionState)compart}]; - }); + param_percentil(node, [compart, i](auto&& model) -> auto& { + return model.populations[{i, (InfectionState)compart}]; + }); } // times - param_percentil( - node, [i](auto&& model) -> auto& { return model.parameters.template get>()[i]; }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); //probs - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); } // group independent params - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { - return model.parameters.template get>(); - }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][node]; single_element_ensemble[run] = - params.parameters.template get>() * params.populations.get_total(); + params.parameters.template get>() * params.populations.get_total(); } std::sort(single_element_ensemble.begin(), single_element_ensemble.end()); - percentile[node].parameters.template set>( + percentile[node].parameters.template set>( single_element_ensemble[static_cast(num_runs * p)]); } return percentile; diff --git a/cpp/models/ode_secir/parameters_io.cpp b/cpp/models/ode_secir/parameters_io.cpp index 621efbf276..5515cd1625 100644 --- a/cpp/models/ode_secir/parameters_io.cpp +++ b/cpp/models/ode_secir/parameters_io.cpp @@ -43,164 +43,6 @@ namespace osecir namespace details { -//overload for integers, so the comparison of data entry to integers is symmetric (required by e.g. equal_range) -int get_region_id(int id) -{ - return id; -} - -IOResult read_confirmed_cases_data( - std::vector& rki_data, std::vector const& vregion, Date date, - std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, - std::vector>& vnum_InfectedSymptoms, std::vector>& vnum_InfectedSevere, - std::vector>& vnum_icu, std::vector>& vnum_death, - std::vector>& vnum_rec, const std::vector>& vt_Exposed, - const std::vector>& vt_InfectedNoSymptoms, - const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, - const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, - const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, - const std::vector& scaling_factor_inf) -{ - auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == rki_data.end()) { - log_error("RKI data file is empty."); - return failure(StatusCode::InvalidFileFormat, "RKI file is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("Specified date does not exist in RKI data"); - return failure(StatusCode::OutOfRange, "Specified date does not exist in RKI data."); - } - auto days_surplus = std::min(get_offset_in_days(max_date, date) - 6, 0); - - //this statement causes maybe-uninitialized warning for some versions of gcc. - //the error is reported in an included header, so the warning is disabled for the whole file - std::sort(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { - return std::make_tuple(get_region_id(a), a.date) < std::make_tuple(get_region_id(b), b.date); - }); - - for (auto region_idx = size_t(0); region_idx < vregion.size(); ++region_idx) { - auto region_entry_range_it = - std::equal_range(rki_data.begin(), rki_data.end(), vregion[region_idx], [](auto&& a, auto&& b) { - return get_region_id(a) < get_region_id(b); - }); - auto region_entry_range = make_range(region_entry_range_it); - if (region_entry_range.begin() == region_entry_range.end()) { - log_error("No entries found for region {}", vregion[region_idx]); - return failure(StatusCode::InvalidFileFormat, - "No entries found for region " + std::to_string(vregion[region_idx])); - } - for (auto&& region_entry : region_entry_range) { - - auto& t_Exposed = vt_Exposed[region_idx]; - auto& t_InfectedNoSymptoms = vt_InfectedNoSymptoms[region_idx]; - auto& t_InfectedSymptoms = vt_InfectedSymptoms[region_idx]; - auto& t_InfectedSevere = vt_InfectedSevere[region_idx]; - auto& t_InfectedCritical = vt_InfectedCritical[region_idx]; - - auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; - auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; - auto& num_rec = vnum_rec[region_idx]; - auto& num_Exposed = vnum_Exposed[region_idx]; - auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; - auto& num_death = vnum_death[region_idx]; - auto& num_icu = vnum_icu[region_idx]; - - auto& mu_C_R = vmu_C_R[region_idx]; - auto& mu_I_H = vmu_I_H[region_idx]; - auto& mu_H_U = vmu_H_U[region_idx]; - - auto date_df = region_entry.date; - auto age = size_t(region_entry.age_group); - - if (date_df == offset_date_by_days(date, 0)) { - num_InfectedSymptoms[age] += scaling_factor_inf[age] * region_entry.num_confirmed; - // We intentionally do NOT multiply recovered with the scaling_factor_inf here. - // If we apply the scaling factor to recovered as well, we would implicitly - // assume that this factor holds for the entire historical period up to t0, which is not valid. - num_rec[age] += region_entry.num_confirmed; - } - if (date_df == offset_date_by_days(date, days_surplus)) { - num_InfectedNoSymptoms[age] -= - 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; - } - if (date_df == offset_date_by_days(date, t_InfectedNoSymptoms[age] + days_surplus)) { - num_InfectedNoSymptoms[age] += - 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; - num_Exposed[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; - } - if (date_df == offset_date_by_days(date, t_Exposed[age] + t_InfectedNoSymptoms[age] + days_surplus)) { - num_Exposed[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; - } - if (date_df == offset_date_by_days(date, -t_InfectedSymptoms[age])) { - num_InfectedSymptoms[age] -= scaling_factor_inf[age] * region_entry.num_confirmed; - num_InfectedSevere[age] += mu_I_H[age] * scaling_factor_inf[age] * region_entry.num_confirmed; - } - if (date_df == offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age])) { - num_InfectedSevere[age] -= mu_I_H[age] * scaling_factor_inf[age] * region_entry.num_confirmed; - num_icu[age] += mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * region_entry.num_confirmed; - } - if (date_df == - offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age] - t_InfectedCritical[age])) { - num_death[age] += region_entry.num_deaths; - num_icu[age] -= mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * region_entry.num_confirmed; - } - } - } - - for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { - auto region = vregion[region_idx]; - - auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; - auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; - auto& num_rec = vnum_rec[region_idx]; - auto& num_Exposed = vnum_Exposed[region_idx]; - auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; - auto& num_death = vnum_death[region_idx]; - auto& num_icu = vnum_icu[region_idx]; - - for (size_t i = 0; i < ConfirmedCasesDataEntry::age_group_names.size(); i++) { - // R(t0) = ΣC(t0) − I(t0) − H(t0) − U(t0) − D(t0) - // subtract currently infectious/hospitalized/ICU/dead - // Note: We divide I/H/U/D by scaling_factor_inf to "unscale" these contributions back to the - // reported level before subtracting from recovered. If we also applied the scaling factor to - // recovered, we would implicitly assume that the same underreporting applies to the entire - // history up to t0, which would be wrong. The scaling factor should reflect underreporting - // around t0 only. - num_rec[i] -= - (num_InfectedSymptoms[i] / scaling_factor_inf[i] + num_InfectedSevere[i] / scaling_factor_inf[i] + - num_icu[i] / scaling_factor_inf[i] + num_death[i] / scaling_factor_inf[i]); - - auto try_fix_constraints = [region, i](double& value, double error, auto str) { - if (value < error) { - //this should probably return a failure - //but the algorithm is not robust enough to avoid large negative values and there are tests that rely on it - log_error("{:s} for age group {:s} is {:.4f} for region {:d}, exceeds expected negative value.", - str, ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - else if (value < 0) { - log_info("{:s} for age group {:s} is {:.4f} for region {:d}, automatically corrected", str, - ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - }; - - try_fix_constraints(num_InfectedSymptoms[i], -5, "InfectedSymptoms"); - try_fix_constraints(num_InfectedNoSymptoms[i], -5, "InfectedNoSymptoms"); - try_fix_constraints(num_Exposed[i], -5, "Exposed"); - try_fix_constraints(num_InfectedSevere[i], -5, "InfectedSevere"); - try_fix_constraints(num_death[i], -5, "Dead"); - try_fix_constraints(num_icu[i], -5, "InfectedCritical"); - try_fix_constraints(num_rec[i], -20, "Recovered"); - } - } - - return success(); -} - } // namespace details } // namespace osecir } // namespace mio diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 8471e25a00..ffeaf52b9a 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -29,6 +29,7 @@ #include "memilio/io/epi_data.h" #include "memilio/io/parameters_io.h" #include "memilio/io/result_io.h" +#include "memilio/math/math_utils.h" namespace mio { @@ -38,6 +39,13 @@ namespace osecir namespace details { + +//overload for integers, so the comparison of data entry to integers is symmetric (required by e.g. equal_range) +inline int get_region_id(int id) +{ + return id; +} + /** * @brief reads populations data from RKI. * @param[in] rki_data Vector of ConfirmedCasesDataEntry%s. @@ -48,17 +56,158 @@ namespace details * @param[in] vmu_* vector Probabilities to get from one compartement to another for each age group. * @param[in] scaling_factor_inf Factors by which to scale the confirmed cases of rki data. */ +template IOResult read_confirmed_cases_data( std::vector& rki_data, std::vector const& vregion, Date date, - std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, - std::vector>& vnum_InfectedSymptoms, std::vector>& vnum_InfectedSevere, - std::vector>& vnum_icu, std::vector>& vnum_death, - std::vector>& vnum_rec, const std::vector>& vt_Exposed, + std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, + std::vector>& vnum_InfectedSymptoms, std::vector>& vnum_InfectedSevere, + std::vector>& vnum_icu, std::vector>& vnum_death, + std::vector>& vnum_rec, const std::vector>& vt_Exposed, const std::vector>& vt_InfectedNoSymptoms, const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, - const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, - const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, - const std::vector& scaling_factor_inf); + const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, + const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, + const std::vector& scaling_factor_inf) +{ + auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { + return a.date < b.date; + }); + if (max_date_entry == rki_data.end()) { + log_error("RKI data file is empty."); + return failure(StatusCode::InvalidFileFormat, "RKI file is empty."); + } + auto max_date = max_date_entry->date; + if (max_date < date) { + log_error("Specified date does not exist in RKI data"); + return failure(StatusCode::OutOfRange, "Specified date does not exist in RKI data."); + } + int days_surplus = std::min(get_offset_in_days(max_date, date) - 6, 0); + + //this statement causes maybe-uninitialized warning for some versions of gcc. + //the error is reported in an included header, so the warning is disabled for the whole file + std::sort(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { + return std::make_tuple(get_region_id(a), a.date) < std::make_tuple(get_region_id(b), b.date); + }); + + for (auto region_idx = size_t(0); region_idx < vregion.size(); ++region_idx) { + auto region_entry_range_it = + std::equal_range(rki_data.begin(), rki_data.end(), vregion[region_idx], [](auto&& a, auto&& b) { + return get_region_id(a) < get_region_id(b); + }); + auto region_entry_range = make_range(region_entry_range_it); + if (region_entry_range.begin() == region_entry_range.end()) { + log_error("No entries found for region {}", vregion[region_idx]); + return failure(StatusCode::InvalidFileFormat, + "No entries found for region " + std::to_string(vregion[region_idx])); + } + for (auto&& region_entry : region_entry_range) { + + auto& t_Exposed = vt_Exposed[region_idx]; + auto& t_InfectedNoSymptoms = vt_InfectedNoSymptoms[region_idx]; + auto& t_InfectedSymptoms = vt_InfectedSymptoms[region_idx]; + auto& t_InfectedSevere = vt_InfectedSevere[region_idx]; + auto& t_InfectedCritical = vt_InfectedCritical[region_idx]; + + auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; + auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; + auto& num_rec = vnum_rec[region_idx]; + auto& num_Exposed = vnum_Exposed[region_idx]; + auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; + auto& num_death = vnum_death[region_idx]; + auto& num_icu = vnum_icu[region_idx]; + + auto& mu_C_R = vmu_C_R[region_idx]; + auto& mu_I_H = vmu_I_H[region_idx]; + auto& mu_H_U = vmu_H_U[region_idx]; + + auto date_df = region_entry.date; + auto age = size_t(region_entry.age_group); + + if (date_df == offset_date_by_days(date, 0)) { + num_InfectedSymptoms[age] += scaling_factor_inf[age] * region_entry.num_confirmed; + // We intentionally do NOT multiply recovered with the scaling_factor_inf here. + // If we apply the scaling factor to recovered as well, we would implicitly + // assume that this factor holds for the entire historical period up to t0, which is not valid. + num_rec[age] += region_entry.num_confirmed; + } + if (date_df == offset_date_by_days(date, days_surplus)) { + num_InfectedNoSymptoms[age] -= + 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; + } + if (date_df == offset_date_by_days(date, t_InfectedNoSymptoms[age] + days_surplus)) { + num_InfectedNoSymptoms[age] += + 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; + num_Exposed[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; + } + if (date_df == offset_date_by_days(date, t_Exposed[age] + t_InfectedNoSymptoms[age] + days_surplus)) { + num_Exposed[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * region_entry.num_confirmed; + } + if (date_df == offset_date_by_days(date, -t_InfectedSymptoms[age])) { + num_InfectedSymptoms[age] -= scaling_factor_inf[age] * region_entry.num_confirmed; + num_InfectedSevere[age] += mu_I_H[age] * scaling_factor_inf[age] * region_entry.num_confirmed; + } + if (date_df == offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age])) { + num_InfectedSevere[age] -= mu_I_H[age] * scaling_factor_inf[age] * region_entry.num_confirmed; + num_icu[age] += mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * region_entry.num_confirmed; + } + if (date_df == + offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age] - t_InfectedCritical[age])) { + num_death[age] += region_entry.num_deaths; + num_icu[age] -= mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * region_entry.num_confirmed; + } + } + } + + for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { + auto region = vregion[region_idx]; + + auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; + auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; + auto& num_rec = vnum_rec[region_idx]; + auto& num_Exposed = vnum_Exposed[region_idx]; + auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; + auto& num_death = vnum_death[region_idx]; + auto& num_icu = vnum_icu[region_idx]; + + for (size_t i = 0; i < ConfirmedCasesDataEntry::age_group_names.size(); i++) { + // R(t0) = ΣC(t0) − I(t0) − H(t0) − U(t0) − D(t0) + // subtract currently infectious/hospitalized/ICU/dead + // Note: We divide I/H/U/D by scaling_factor_inf to "unscale" these contributions back to the + // reported level before subtracting from recovered. If we also applied the scaling factor to + // recovered, we would implicitly assume that the same underreporting applies to the entire + // history up to t0, which would be wrong. The scaling factor should reflect underreporting + // around t0 only. + num_rec[i] -= + (num_InfectedSymptoms[i] / scaling_factor_inf[i] + num_InfectedSevere[i] / scaling_factor_inf[i] + + num_icu[i] / scaling_factor_inf[i] + num_death[i] / scaling_factor_inf[i]); + + auto try_fix_constraints = [region, i](FP& value, FP error, auto str) { + if (value < error) { + //this should probably return a failure + //but the algorithm is not robust enough to avoid large negative values and there are tests that rely on it + log_error("{:s} for age group {:s} is {:.4f} for region {:d}, exceeds expected negative value.", + str, ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + else if (value < 0) { + log_info("{:s} for age group {:s} is {:.4f} for region {:d}, automatically corrected", str, + ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + }; + + try_fix_constraints(num_InfectedSymptoms[i], -5, "InfectedSymptoms"); + try_fix_constraints(num_InfectedNoSymptoms[i], -5, "InfectedNoSymptoms"); + try_fix_constraints(num_Exposed[i], -5, "Exposed"); + try_fix_constraints(num_InfectedSevere[i], -5, "InfectedSevere"); + try_fix_constraints(num_death[i], -5, "Dead"); + try_fix_constraints(num_icu[i], -5, "InfectedCritical"); + try_fix_constraints(num_rec[i], -20, "Recovered"); + } + } + + return success(); +} /** * @brief Sets populations data from already read case data with multiple age groups into a Model with one age group. @@ -69,17 +218,17 @@ IOResult read_confirmed_cases_data( * @param[in] date Date at which the data is read. * @param[in] scaling_factor_inf Factors by which to scale the confirmed cases of rki data. */ -template +template IOResult set_confirmed_cases_data(std::vector>& model, std::vector& case_data, const std::vector& region, Date date, - const std::vector& scaling_factor_inf) + const std::vector& scaling_factor_inf) { const size_t num_age_groups = ConfirmedCasesDataEntry::age_group_names.size(); // allow single scalar scaling that is broadcast to all age groups assert(scaling_factor_inf.size() == 1 || scaling_factor_inf.size() == num_age_groups); // Set scaling factors to match num age groups - std::vector scaling_factor_inf_full; + std::vector scaling_factor_inf_full; if (scaling_factor_inf.size() == 1) { scaling_factor_inf_full.assign(num_age_groups, scaling_factor_inf[0]); } @@ -93,10 +242,10 @@ IOResult set_confirmed_cases_data(std::vector>& model, std::vect std::vector> t_InfectedSevere{model.size()}; std::vector> t_InfectedCritical{model.size()}; - std::vector> mu_C_R{model.size()}; - std::vector> mu_I_H{model.size()}; - std::vector> mu_H_U{model.size()}; - std::vector> mu_U_D{model.size()}; + std::vector> mu_C_R{model.size()}; + std::vector> mu_I_H{model.size()}; + std::vector> mu_H_U{model.size()}; + std::vector> mu_U_D{model.size()}; for (size_t node = 0; node < model.size(); ++node) { const size_t model_groups = (size_t)model[node].parameters.get_num_groups(); @@ -125,13 +274,13 @@ IOResult set_confirmed_cases_data(std::vector>& model, std::vect mu_U_D[node].push_back(model[node].parameters.template get>()[(AgeGroup)group]); } } - std::vector> num_InfectedSymptoms(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_death(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_rec(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_Exposed(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_InfectedNoSymptoms(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_InfectedSevere(model.size(), std::vector(num_age_groups, 0.0)); - std::vector> num_icu(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_InfectedSymptoms(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_death(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_rec(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_Exposed(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_InfectedNoSymptoms(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_InfectedSevere(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> num_icu(model.size(), std::vector(num_age_groups, 0.0)); BOOST_OUTCOME_TRY(read_confirmed_cases_data(case_data, region, date, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_rec, @@ -139,7 +288,10 @@ IOResult set_confirmed_cases_data(std::vector>& model, std::vect t_InfectedCritical, mu_C_R, mu_I_H, mu_H_U, scaling_factor_inf_full)); for (size_t node = 0; node < model.size(); node++) { - if (std::accumulate(num_InfectedSymptoms[node].begin(), num_InfectedSymptoms[node].end(), 0.0) > 0) { + if (std::accumulate(num_InfectedSymptoms[node].begin(), num_InfectedSymptoms[node].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) > 0.0) { size_t num_groups = (size_t)model[node].parameters.get_num_groups(); if (num_groups == num_age_groups) { for (size_t i = 0; i < num_groups; i++) { @@ -161,8 +313,10 @@ IOResult set_confirmed_cases_data(std::vector>& model, std::vect } } else { - const auto sum_vec = [](const std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0.0); + const auto sum_vec = [](const std::vector& v) { + return std::accumulate(v.begin(), v.end(), FP(0.0), [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }); }; const size_t i0 = 0; model[node].populations[{AgeGroup(i0), InfectionState::Exposed}] = sum_vec(num_Exposed[node]); @@ -199,13 +353,13 @@ IOResult set_confirmed_cases_data(std::vector>& model, std::vect * @param[in] date Date at which the data is read. * @param[in] scaling_factor_inf Factors by which to scale the confirmed cases of rki data. */ -template +template IOResult set_confirmed_cases_data(std::vector>& model, const std::string& path, std::vector const& region, Date date, - const std::vector& scaling_factor_inf) + const std::vector& scaling_factor_inf) { BOOST_OUTCOME_TRY(auto&& case_data, mio::read_confirmed_cases_data(path)); - BOOST_OUTCOME_TRY(set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf)); + BOOST_OUTCOME_TRY(set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf)); return success(); } @@ -218,9 +372,9 @@ IOResult set_confirmed_cases_data(std::vector>& model, const std * @param[in] date Date for which the arrays are initialized. * @param[in] scaling_factor_icu factor by which to scale the icu cases of divi data. */ -template +template IOResult set_divi_data(std::vector>& model, const std::string& path, const std::vector& vregion, - Date date, double scaling_factor_icu) + Date date, FP scaling_factor_icu) { // DIVI dataset will no longer be updated from CW29 2024 on. if (!is_divi_data_available(date)) { @@ -229,8 +383,8 @@ IOResult set_divi_data(std::vector>& model, const std::string& p date); return success(); } - std::vector sum_mu_I_U(vregion.size(), 0); - std::vector> mu_I_U{model.size()}; + std::vector sum_mu_I_U(vregion.size(), 0); + std::vector> mu_I_U{model.size()}; for (size_t region = 0; region < vregion.size(); region++) { auto num_groups = model[region].parameters.get_num_groups(); for (auto i = AgeGroup(0); i < num_groups; i++) { @@ -240,8 +394,8 @@ IOResult set_divi_data(std::vector>& model, const std::string& p model[region].parameters.template get>()[i]); } } - std::vector num_icu(model.size(), 0.0); - BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); + std::vector num_icu(model.size(), 0.0); + BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); for (size_t region = 0; region < vregion.size(); region++) { auto num_groups = model[region].parameters.get_num_groups(); @@ -261,9 +415,8 @@ IOResult set_divi_data(std::vector>& model, const std::string& p * @param[in] num_population Vector of population data. The size should be the same as vregion and model. * @param[in] vregion Vector of keys of the regions of interest. */ -template -IOResult set_population_data(std::vector>& model, - const std::vector>& num_population, +template +IOResult set_population_data(std::vector>& model, const std::vector>& num_population, const std::vector& vregion) { assert(num_population.size() == vregion.size()); @@ -280,7 +433,10 @@ IOResult set_population_data(std::vector>& model, } } else if (model_groups == 1 && data_groups >= 1) { - const double total = std::accumulate(num_population[region].begin(), num_population[region].end(), 0.0); + const FP total = std::accumulate(num_population[region].begin(), num_population[region].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }); model[region].populations.template set_difference_from_group_total( {AgeGroup(0), InfectionState::Susceptible}, total); } @@ -295,12 +451,12 @@ IOResult set_population_data(std::vector>& model, * @param[in] path Path to RKI file containing population data. * @param[in] vregion Vector of keys of the regions of interest. */ -template +template IOResult set_population_data(std::vector>& model, const std::string& path, const std::vector& vregion) { - BOOST_OUTCOME_TRY(const auto&& num_population, read_population_data(path, vregion)); - BOOST_OUTCOME_TRY(set_population_data(model, num_population, vregion)); + BOOST_OUTCOME_TRY(const auto&& num_population, read_population_data(path, vregion)); + BOOST_OUTCOME_TRY(set_population_data(model, num_population, vregion)); return success(); } @@ -324,48 +480,53 @@ IOResult set_population_data(std::vector>& model, const std::str * @param[in] confirmed_cases_path Path to confirmed cases file. * @param[in] population_data_path Path to population data file. */ -template -IOResult export_input_data_county_timeseries( - std::vector models, const std::string& results_dir, std::vector const& region, Date date, - const std::vector& scaling_factor_inf, double scaling_factor_icu, int num_days, - const std::string& divi_data_path, const std::string& confirmed_cases_path, const std::string& population_data_path) +template +IOResult export_input_data_county_timeseries(std::vector models, const std::string& results_dir, + std::vector const& region, Date date, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, + int num_days, const std::string& divi_data_path, + const std::string& confirmed_cases_path, + const std::string& population_data_path) { const auto num_age_groups = (size_t)models[0].parameters.get_num_groups(); // allow scalar scaling factor as convenience for 1-group models assert(scaling_factor_inf.size() == 1 || scaling_factor_inf.size() == num_age_groups); assert(models.size() == region.size()); - std::vector> extrapolated_data( - region.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_age_groups)); + std::vector> extrapolated_data( + region.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_age_groups)); - BOOST_OUTCOME_TRY(auto&& num_population, mio::read_population_data(population_data_path, region)); + BOOST_OUTCOME_TRY(auto&& num_population, mio::read_population_data(population_data_path, region)); BOOST_OUTCOME_TRY(auto&& case_data, mio::read_confirmed_cases_data(confirmed_cases_path)); for (int t = 0; t <= num_days; ++t) { auto offset_day = offset_date_by_days(date, t); - BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, region, offset_day, scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(models, case_data, region, offset_day, scaling_factor_inf)); - BOOST_OUTCOME_TRY(details::set_population_data(models, num_population, region)); + BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, region, offset_day, scaling_factor_icu)); + BOOST_OUTCOME_TRY( + details::set_confirmed_cases_data(models, case_data, region, offset_day, scaling_factor_inf)); + BOOST_OUTCOME_TRY(details::set_population_data(models, num_population, region)); for (size_t r = 0; r < region.size(); r++) { extrapolated_data[r][t] = models[r].get_initial_values(); } } BOOST_OUTCOME_TRY( - save_result(extrapolated_data, region, (int)num_age_groups, path_join(results_dir, "Results_rki.h5"))); + save_result(extrapolated_data, region, (int)num_age_groups, path_join(results_dir, "Results_rki.h5"))); - auto rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); + auto rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); BOOST_OUTCOME_TRY( - save_result({rki_data_sum[0][0]}, {0}, (int)num_age_groups, path_join(results_dir, "Results_rki_sum.h5"))); + save_result({rki_data_sum[0][0]}, {0}, (int)num_age_groups, path_join(results_dir, "Results_rki_sum.h5"))); return success(); } #else -template -IOResult export_input_data_county_timeseries( - std::vector models, const std::string& results_dir, std::vector const& region, Date date, - const std::vector& scaling_factor_inf, double scaling_factor_icu, int num_days, - const std::string& divi_data_path, const std::string& confirmed_cases_path, const std::string& population_data_path) +template +IOResult export_input_data_county_timeseries(std::vector models, const std::string& results_dir, + std::vector const& region, Date date, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, + int num_days, const std::string& divi_data_path, + const std::string& confirmed_cases_path, + const std::string& population_data_path) { mio::log_warning("HDF5 not available. Cannot export time series of extrapolated real data."); return success(); @@ -380,10 +541,9 @@ IOResult export_input_data_county_timeseries( * @param[in] scaling_factor_icu Factor by which to scale the icu cases of divi data. * @param[in] pydata_dir Directory of files. */ -template -IOResult read_input_data_germany(std::vector& model, Date date, - const std::vector& scaling_factor_inf, double scaling_factor_icu, - const std::string& pydata_dir) +template +IOResult read_input_data_germany(std::vector& model, Date date, const std::vector& scaling_factor_inf, + FP scaling_factor_icu, const std::string& pydata_dir) { BOOST_OUTCOME_TRY( details::set_divi_data(model, path_join(pydata_dir, "germany_divi.json"), {0}, date, scaling_factor_icu)); @@ -403,18 +563,18 @@ IOResult read_input_data_germany(std::vector& model, Date date, * @param[in] scaling_factor_icu Factor by which to scale the icu cases of divi data. * @param[in] pydata_dir Directory of files. */ -template +template IOResult read_input_data_state(std::vector& model, Date date, std::vector& state, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir) { BOOST_OUTCOME_TRY( - details::set_divi_data(model, path_join(pydata_dir, "state_divi.json"), state, date, scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "cases_all_state_age_ma7.json"), - state, date, scaling_factor_inf)); + details::set_divi_data(model, path_join(pydata_dir, "state_divi.json"), state, date, scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data( + model, path_join(pydata_dir, "cases_all_state_age_ma7.json"), state, date, scaling_factor_inf)); BOOST_OUTCOME_TRY( - details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), state)); + details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), state)); return success(); } @@ -429,17 +589,17 @@ IOResult read_input_data_state(std::vector& model, Date date, std:: * @param[in] num_days [Default: 0] Number of days to be simulated; required to extrapolate real data. * @param[in] export_time_series [Default: false] If true, reads data for each day of simulation and writes it in the same directory as the input files. */ -template +template IOResult read_input_data_county(std::vector& model, Date date, const std::vector& county, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days = 0, bool export_time_series = false) { + BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "county_divi_ma7.json"), county, date, + scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data( + model, path_join(pydata_dir, "cases_all_county_age_ma7.json"), county, date, scaling_factor_inf)); BOOST_OUTCOME_TRY( - details::set_divi_data(model, path_join(pydata_dir, "county_divi_ma7.json"), county, date, scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "cases_all_county_age_ma7.json"), - county, date, scaling_factor_inf)); - BOOST_OUTCOME_TRY( - details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), county)); + details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), county)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -447,7 +607,7 @@ IOResult read_input_data_county(std::vector& model, Date date, cons // (This only represents the vectorization of the previous function over all simulation days...) log_warning("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, county, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "county_divi_ma7.json"), path_join(pydata_dir, "cases_all_county_age_ma7.json"), path_join(pydata_dir, "county_current_population.json"))); @@ -465,16 +625,16 @@ IOResult read_input_data_county(std::vector& model, Date date, cons * @param[in] pydata_dir directory of files * @param[in] age_group_names strings specifying age group names */ -template +template IOResult read_input_data(std::vector& model, Date date, const std::vector& node_ids, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days = 0, bool export_time_series = false) { - BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, - scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), node_ids, - date, scaling_factor_inf)); - BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), node_ids)); + BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, + scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), + node_ids, date, scaling_factor_inf)); + BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), node_ids)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -482,7 +642,7 @@ IOResult read_input_data(std::vector& model, Date date, const std:: // (This only represents the vectorization of the previous function over all simulation days...) log_warning("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, node_ids, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "critical_cases.json"), path_join(pydata_dir, "confirmed_cases.json"), path_join(pydata_dir, "population_data.json"))); diff --git a/cpp/models/ode_secirts/analyze_result.cpp b/cpp/models/ode_secirts/analyze_result.cpp index 0b50d07784..29c216e80c 100644 --- a/cpp/models/ode_secirts/analyze_result.cpp +++ b/cpp/models/ode_secirts/analyze_result.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker diff --git a/cpp/models/ode_secirts/analyze_result.h b/cpp/models/ode_secirts/analyze_result.h index 180a26e7f7..857658ffa3 100644 --- a/cpp/models/ode_secirts/analyze_result.h +++ b/cpp/models/ode_secirts/analyze_result.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker, Wadim Koslow, Daniel Abele, Martin J. Kühn @@ -37,8 +37,8 @@ namespace osecirts * @param p percentile value in open interval (0, 1) * @return p percentile of the parameters over all runs */ -template -std::vector ensemble_params_percentile(const std::vector>& ensemble_params, double p) +template +std::vector ensemble_params_percentile(const std::vector>& ensemble_params, FP p) { assert(p > 0.0 && p < 1.0 && "Invalid percentile value."); @@ -46,15 +46,15 @@ std::vector ensemble_params_percentile(const std::vector>() + .parameters.template get>() .template size(); - std::vector single_element_ensemble(num_runs); + std::vector single_element_ensemble(num_runs); // lambda function that calculates the percentile of a single parameter std::vector percentile(num_nodes, Model((int)num_groups)); auto param_percentil = [&ensemble_params, p, num_runs, &percentile](auto n, auto get_param) mutable { - std::vector single_element(num_runs); + std::vector single_element(num_runs); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][n]; single_element[run] = get_param(params); @@ -65,148 +65,125 @@ std::vector ensemble_params_percentile(const std::vector>().resize(num_days); - percentile[node].parameters.template get>().resize(num_days); - percentile[node].parameters.template get>().resize(num_days); + percentile[node].parameters.template get>().resize(num_days); + percentile[node].parameters.template get>().resize(num_days); + percentile[node].parameters.template get>().resize(num_days); for (auto i = AgeGroup(0); i < AgeGroup(num_groups); i++) { //Population for (auto compart = Index(0); compart < InfectionState::Count; ++compart) { - param_percentil( - node, [ compart, i ](auto&& model) -> auto& { - return model.populations[{i, compart}]; - }); + param_percentil(node, [compart, i](auto&& model) -> auto& { + return model.populations[{i, compart}]; + }); } // times - param_percentil( - node, [i](auto&& model) -> auto& { return model.parameters.template get>()[i]; }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); //probs - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); //vaccinations - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + + for (auto day = SimulationDay(0); day < num_days; ++day) { + param_percentil(node, [i, day](auto&& model) -> auto& { + return model.parameters.template get>()[{i, day}]; }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; + param_percentil(node, [i, day](auto&& model) -> auto& { + return model.parameters.template get>()[{i, day}]; }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; + param_percentil(node, [i, day](auto&& model) -> auto& { + return model.parameters.template get>()[{i, day}]; }); - - for (auto day = SimulationDay(0); day < num_days; ++day) { - param_percentil( - node, [ i, day ](auto&& model) -> auto& { - return model.parameters.template get>()[{i, day}]; - }); - param_percentil( - node, [ i, day ](auto&& model) -> auto& { - return model.parameters.template get>()[{i, day}]; - }); - param_percentil( - node, [ i, day ](auto&& model) -> auto& { - return model.parameters.template get>()[{i, day}]; - }); } //virus variants - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); } // group independent params - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { - return model.parameters.template get>(); - }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][node]; single_element_ensemble[run] = - params.parameters.template get>() * params.populations.get_total(); + params.parameters.template get>() * params.populations.get_total(); } std::sort(single_element_ensemble.begin(), single_element_ensemble.end()); - percentile[node].parameters.template set>( + percentile[node].parameters.template set>( single_element_ensemble[static_cast(num_runs * p)]); } return percentile; diff --git a/cpp/models/ode_secirts/parameters_io.cpp b/cpp/models/ode_secirts/parameters_io.cpp index b5df44756d..0764859b02 100644 --- a/cpp/models/ode_secirts/parameters_io.cpp +++ b/cpp/models/ode_secirts/parameters_io.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker, Wadim Koslow, Daniel Abele, Martin J. Kühn diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index 537a838f6d..c43db73b30 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker, Wadim Koslow, Daniel Abele, Martin J. Kühn @@ -27,6 +27,7 @@ #include "ode_secirts/model.h" #include "ode_secirts/analyze_result.h" #include "memilio/math/eigen_util.h" +#include "memilio/math/math_utils.h" #include "memilio/mobility/graph.h" #include "memilio/mobility/metapopulation_mobility_instant.h" #include "memilio/io/epi_data.h" @@ -47,8 +48,8 @@ namespace details /** * @brief Computes the distribution of confirmed cases across infection states based on Case (RKI) data. * - * This function processes case data for given regions and distributes the cases across different - * infection states, considering the corresponding transition times and probabilities defined in the model. + * This function processes case data for given regions and distributes the cases across different + * infection states, considering the corresponding transition times and probabilities defined in the model. * * @tparam Model The type of the model used. * @tparam FP Floating point type (default: double). @@ -64,12 +65,12 @@ namespace details * @param[in] vregion Vector of region IDs representing the regions in the model vector. * @param[in] date Date for which the simulation starts. * @param[in] model Vector of models, each representing a region and containing the parameters. - * @param[in] scaling_factor_inf Vector of scaling factors for confirmed cases for + * @param[in] scaling_factor_inf Vector of scaling factors for confirmed cases for * @param[in] layer Specifies the immunity layer: 0 (Naive), 1 (Partial Immunity), 2 (Improved Immunity). * * @return An IOResult showing success or failure. */ -template +template IOResult compute_confirmed_cases_data( const std::vector& case_data, std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, std::vector>& vnum_InfectedSymptoms, @@ -78,6 +79,8 @@ IOResult compute_confirmed_cases_data( std::vector const& vregion, Date date, const std::vector& model, const std::vector& scaling_factor_inf, const size_t layer) { + using std::round; + auto max_date_entry = std::max_element(case_data.begin(), case_data.end(), [](auto&& a, auto&& b) { return a.date < b.date; }); @@ -118,17 +121,17 @@ IOResult compute_confirmed_cases_data( auto age = (size_t)entry.age_group; // (rounded) transition times const int t_exposed = - static_cast(std::round(params_region.template get>()[entry.age_group])); + static_cast(round(params_region.template get>()[entry.age_group])); int t_InfectedNoSymptoms = - static_cast(std::round(params_region.template get>()[entry.age_group])); + static_cast(round(params_region.template get>()[entry.age_group])); int t_InfectedSymptoms = - static_cast(std::round(params_region.template get>()[entry.age_group])); + static_cast(round(params_region.template get>()[entry.age_group])); const int t_InfectedSevere = - static_cast(std::round(params_region.template get>()[entry.age_group])); + static_cast(round(params_region.template get>()[entry.age_group])); const int t_InfectedCritical = - static_cast(std::round(params_region.template get>()[entry.age_group])); - const int t_imm_interval_i = static_cast( - std::round(params_region.template get>()[entry.age_group])); + static_cast(round(params_region.template get>()[entry.age_group])); + const int t_imm_interval_i = + static_cast(round(params_region.template get>()[entry.age_group])); // transition probabilities FP recoveredPerInfectedNoSymptoms = @@ -249,7 +252,7 @@ IOResult compute_confirmed_cases_data( /** * @brief Reads confirmed case data from a file and computes the distribution of cases across infection states. * - * This function reads transformed RKI data from a specified file and processes the confirmed cases + * This function reads transformed RKI data from a specified file and processes the confirmed cases * to distribute them across different infection states and age groups. * * @tparam Model The type of the model used. @@ -271,7 +274,7 @@ IOResult compute_confirmed_cases_data( * * @return An IOResult indicating success or failure. */ -template +template IOResult read_confirmed_cases_data( std::string const& path, std::vector const& vregion, Date date, std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, std::vector>& vnum_InfectedSymptoms, @@ -280,15 +283,15 @@ IOResult read_confirmed_cases_data( const std::vector& model, const std::vector& scaling_factor_inf, const size_t layer) { BOOST_OUTCOME_TRY(auto&& case_data, mio::read_confirmed_cases_data(path)); - return compute_confirmed_cases_data(case_data, vnum_Exposed, vnum_InfectedNoSymptoms, vnum_InfectedSymptoms, - vnum_InfectedSevere, vnum_icu, vnum_death, vnum_timm_i, vregion, date, model, - scaling_factor_inf, layer); + return compute_confirmed_cases_data(case_data, vnum_Exposed, vnum_InfectedNoSymptoms, vnum_InfectedSymptoms, + vnum_InfectedSevere, vnum_icu, vnum_death, vnum_timm_i, vregion, date, + model, scaling_factor_inf, layer); } /** * @brief Sets the confirmed cases data in the model considering different immunity layers. * - * This function distributes confirmed case data across infection states for regions and age groups + * This function distributes confirmed case data across infection states for regions and age groups * in the model. It considers different levels of immunity (naive, partial, and improved). * * @tparam Model The type of the model used. @@ -303,7 +306,7 @@ IOResult read_confirmed_cases_data( * * @return An IOResult indicating success or failure. */ -template +template IOResult set_confirmed_cases_data(std::vector& model, const std::vector& case_data, std::vector const& region, Date date, const std::vector& scaling_factor_inf, @@ -373,9 +376,9 @@ set_confirmed_cases_data(std::vector& model, const std::vector(case_data, num_Exposed, num_InfectedNoSymptoms, + num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, + num_timm1, region, date, model, scaling_factor_inf, 0)); for (size_t county = 0; county < model.size(); county++) { size_t num_groups = (size_t)model[county].parameters.get_num_groups(); @@ -396,7 +399,10 @@ set_confirmed_cases_data(std::vector& model, const std::vector(a + b); + }) == 0) { log_warning( "No infections for unvaccinated reported on date {} for region {}. Population data has not been set.", date, region[county]); @@ -413,9 +419,9 @@ set_confirmed_cases_data(std::vector& model, const std::vector(num_age_groups, 0.0); } - BOOST_OUTCOME_TRY(compute_confirmed_cases_data(case_data, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, - num_InfectedSevere, num_icu, num_death, num_timm1, region, date, - model, scaling_factor_inf, 1)); + BOOST_OUTCOME_TRY(compute_confirmed_cases_data(case_data, num_Exposed, num_InfectedNoSymptoms, + num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, + num_timm1, region, date, model, scaling_factor_inf, 1)); for (size_t county = 0; county < model.size(); county++) { size_t num_groups = (size_t)model[county].parameters.get_num_groups(); @@ -453,7 +459,10 @@ set_confirmed_cases_data(std::vector& model, const std::vector>()[(AgeGroup)i] * denom_E[i] * num_timm1[county][i]; } - if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) == 0) { + if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) == 0) { log_warning("No infections for partially vaccinated reported on date {} for region {}. " "Population data has not been set.", date, region[county]); @@ -470,9 +479,9 @@ set_confirmed_cases_data(std::vector& model, const std::vector(num_age_groups, 0.0); } - BOOST_OUTCOME_TRY(compute_confirmed_cases_data(case_data, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, - num_InfectedSevere, num_icu, num_death, num_timm2, region, date, - model, scaling_factor_inf, 2)); + BOOST_OUTCOME_TRY(compute_confirmed_cases_data(case_data, num_Exposed, num_InfectedNoSymptoms, + num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, + num_timm2, region, date, model, scaling_factor_inf, 2)); for (size_t county = 0; county < model.size(); county++) { size_t num_groups = (size_t)model[county].parameters.get_num_groups(); @@ -511,7 +520,10 @@ set_confirmed_cases_data(std::vector& model, const std::vector>()[(AgeGroup)i] * denom_E[i] * num_timm2[county][i]; } - if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) == 0) { + if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) == 0) { log_warning("No infections for vaccinated reported on date {} for region {}. " "Population data has not been set.", date, region[county]); @@ -523,8 +535,8 @@ set_confirmed_cases_data(std::vector& model, const std::vector& model, const std::vector +template IOResult set_confirmed_cases_data(std::vector& model, const std::string& path, std::vector const& region, Date date, const std::vector& scaling_factor_inf, @@ -547,15 +559,15 @@ IOResult set_confirmed_cases_data(std::vector& model, const std::st { BOOST_OUTCOME_TRY(auto&& case_data, mio::read_confirmed_cases_data(path)); BOOST_OUTCOME_TRY( - set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf, immunity_population)); + set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf, immunity_population)); return success(); } /** * @brief Sets ICU data from DIVI data into the a vector of models, distributed across age groups. * - * This function reads DIVI data from a file, computes the number of individuals in critical condition (ICU) - * for each region, and sets these values in the model. The ICU cases are distributed across age groups + * This function reads DIVI data from a file, computes the number of individuals in critical condition (ICU) + * for each region, and sets these values in the model. The ICU cases are distributed across age groups * using the transition probabilities from severe to critical. * * @tparam Model The type of the model used. @@ -569,7 +581,7 @@ IOResult set_confirmed_cases_data(std::vector& model, const std::st * * @return An IOResult indicating success or failure. */ -template +template IOResult set_divi_data(std::vector& model, const std::string& path, const std::vector& vregion, Date date, FP scaling_factor_icu) { @@ -592,7 +604,7 @@ IOResult set_divi_data(std::vector& model, const std::string& path, } } std::vector num_icu(model.size(), 0.0); - BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); + BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); for (size_t region = 0; region < vregion.size(); region++) { auto num_groups = model[region].parameters.get_num_groups(); @@ -610,22 +622,25 @@ IOResult set_divi_data(std::vector& model, const std::string& path, * * @tparam Model The type of the model used. * @tparam FP Floating point type (default: double). - * + * * @param[in,out] model A vector of models for which population data will be set. * @param[in] num_population A 2D vector where each row represents the age group population distribution for a specific region. * @param[in] vregion A vector of region identifiers corresponding to the population data. - * @param[in] immunity_population A 2D vector where each row represents the immunity distribution for a specific region + * @param[in] immunity_population A 2D vector where each row represents the immunity distribution for a specific region * across different levels of immunity (e.g., naive, partial, improved immunity). - * + * * @return An IOResult indicating success or failure. */ -template +template IOResult set_population_data(std::vector& model, const std::vector>& num_population, const std::vector& vregion, const std::vector> immunity_population) { for (size_t region = 0; region < vregion.size(); region++) { - if (std::accumulate(num_population[region].begin(), num_population[region].end(), 0.0) > 0) { + if (std::accumulate(num_population[region].begin(), num_population[region].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) > 0.0) { auto num_groups = model[region].parameters.get_num_groups(); for (auto i = AgeGroup(0); i < num_groups; i++) { @@ -684,21 +699,21 @@ IOResult set_population_data(std::vector& model, const std::vector< * @brief Reads population data from a file and sets it for the each given model. * * @tparam Model The type of the model used. - * + * * @param[in,out] model A vector of models for which population data will be set. * @param[in] path The file path to the population data. * @param[in] vregion A vector of region identifiers corresponding to the population data. - * @param[in] immunity_population A 2D vector where each row represents the immunity distribution for a specific region + * @param[in] immunity_population A 2D vector where each row represents the immunity distribution for a specific region * across different levels of immunity (e.g., naive, partial, improved). - * + * * @return An IOResult indicating success or failure. */ -template +template IOResult set_population_data(std::vector& model, const std::string& path, const std::vector& vregion, - const std::vector> immunity_population) + const std::vector> immunity_population) { - BOOST_OUTCOME_TRY(auto&& num_population, mio::read_population_data(path, vregion)); - BOOST_OUTCOME_TRY(set_population_data(model, num_population, vregion, immunity_population)); + BOOST_OUTCOME_TRY(auto&& num_population, mio::read_population_data(path, vregion)); + BOOST_OUTCOME_TRY(set_population_data(model, num_population, vregion, immunity_population)); return success(); } @@ -716,18 +731,20 @@ IOResult set_population_data(std::vector& model, const std::string& * * @return An IOResult indicating success or failure. */ -template +template IOResult set_vaccination_data(std::vector>& model, const std::vector& vacc_data, Date date, const std::vector& vregion, int num_days) { + using std::floor; + auto num_groups = model[0].parameters.get_num_groups(); auto days_until_effective_n = - (int)(double)model[0].parameters.template get>()[AgeGroup(0)]; + (int)(floor(model[0].parameters.template get>()[AgeGroup(0)])); auto days_until_effective_pi = - (int)(double)model[0].parameters.template get>()[AgeGroup(0)]; + (int)(floor(model[0].parameters.template get>()[AgeGroup(0)])); auto days_until_effective_ii = - (int)(double)model[0].parameters.template get>()[AgeGroup(0)]; + (int)(floor(model[0].parameters.template get>()[AgeGroup(0)])); // iterate over regions (e.g., counties) for (size_t i = 0; i < model.size(); ++i) { // iterate over age groups in region @@ -841,7 +858,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::ve * * @return An IOResult indicating success or failure. */ -template +template IOResult set_vaccination_data(std::vector>& model, const std::string& path, Date date, const std::vector& vregion, int num_days) { @@ -867,7 +884,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::st return success(); } BOOST_OUTCOME_TRY(auto&& vacc_data, read_vaccination_data(path)); - BOOST_OUTCOME_TRY(set_vaccination_data(model, vacc_data, date, vregion, num_days)); + BOOST_OUTCOME_TRY(set_vaccination_data(model, vacc_data, date, vregion, num_days)); return success(); } @@ -899,22 +916,22 @@ IOResult set_vaccination_data(std::vector>& model, const std::st * * @return An IOResult indicating success or failure. */ -template +template IOResult export_input_data_county_timeseries( std::vector models, const std::string& results_dir, const std::vector& counties, Date date, - const std::vector& scaling_factor_inf, const double scaling_factor_icu, const int num_days, + const std::vector& scaling_factor_inf, const FP scaling_factor_icu, const int num_days, const std::string& divi_data_path, const std::string& confirmed_cases_path, const std::string& population_data_path, - const std::vector> immunity_population, const std::string& vaccination_data_path = "") + const std::vector> immunity_population, const std::string& vaccination_data_path = "") { const auto num_groups = (size_t)models[0].parameters.get_num_groups(); assert(scaling_factor_inf.size() == num_groups); assert(num_groups == ConfirmedCasesDataEntry::age_group_names.size()); assert(models.size() == counties.size()); - std::vector> extrapolated_data( - models.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_groups)); + std::vector> extrapolated_data( + models.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_groups)); BOOST_OUTCOME_TRY(auto&& case_data, read_confirmed_cases_data(confirmed_cases_path)); - BOOST_OUTCOME_TRY(auto&& population_data, read_population_data(population_data_path, counties)); + BOOST_OUTCOME_TRY(auto&& population_data, read_population_data(population_data_path, counties)); // empty vector if set_vaccination_data is not set std::vector vacc_data; @@ -931,12 +948,12 @@ IOResult export_input_data_county_timeseries( // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. - BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, counties, offset_day, scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, counties, offset_day, scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(models, case_data, counties, offset_day, scaling_factor_inf, - immunity_population)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(models, case_data, counties, offset_day, + scaling_factor_inf, immunity_population)); - BOOST_OUTCOME_TRY(details::set_population_data(models, population_data, counties, immunity_population)); + BOOST_OUTCOME_TRY(details::set_population_data(models, population_data, counties, immunity_population)); for (size_t r = 0; r < counties.size(); r++) { extrapolated_data[r][t] = models[r].get_initial_values(); @@ -949,22 +966,22 @@ IOResult export_input_data_county_timeseries( } } } - BOOST_OUTCOME_TRY(save_result(extrapolated_data, counties, static_cast(num_groups), - path_join(results_dir, "Results_rki.h5"))); + BOOST_OUTCOME_TRY(save_result(extrapolated_data, counties, static_cast(num_groups), + path_join(results_dir, "Results_rki.h5"))); - auto extrapolated_rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); - BOOST_OUTCOME_TRY(save_result({extrapolated_rki_data_sum[0][0]}, {0}, static_cast(num_groups), - path_join(results_dir, "Results_rki_sum.h5"))); + auto extrapolated_rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); + BOOST_OUTCOME_TRY(save_result({extrapolated_rki_data_sum[0][0]}, {0}, static_cast(num_groups), + path_join(results_dir, "Results_rki_sum.h5"))); return success(); } #else -template +template IOResult export_input_data_county_timeseries(std::vector, const std::string&, const std::vector&, - Date, const std::vector&, const double, const int, + Date, const std::vector&, const FP, const int, const std::string&, const std::string&, const std::string&, - const std::vector>, const std::string&) + const std::vector>, const std::string&) { mio::log_warning("HDF5 not available. Cannot export time series of extrapolated real data."); return success(); @@ -993,25 +1010,26 @@ IOResult export_input_data_county_timeseries(std::vector, const std * * @return An IOResult indicating success or failure. */ -template +template IOResult read_input_data_county(std::vector& model, Date date, const std::vector& county, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days, - const std::vector> immunity_population, + const std::vector> immunity_population, bool export_time_series = false) { - BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vacc_county_ageinf_ma7.json"), date, - county, num_days)); + BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vacc_county_ageinf_ma7.json"), + date, county, num_days)); // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. BOOST_OUTCOME_TRY( details::set_divi_data(model, path_join(pydata_dir, "county_divi_ma7.json"), county, date, scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "cases_all_county_age_ma7.json"), - county, date, scaling_factor_inf, immunity_population)); - BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), - county, immunity_population)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, + path_join(pydata_dir, "cases_all_county_age_ma7.json"), + county, date, scaling_factor_inf, immunity_population)); + BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), + county, immunity_population)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -1019,7 +1037,7 @@ IOResult read_input_data_county(std::vector& model, Date date, cons // (This only represents the vectorization of the previous function over all simulation days...) log_info("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, county, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "county_divi_ma7.json"), path_join(pydata_dir, "cases_all_county_age_ma7.json"), path_join(pydata_dir, "county_current_population.json"), immunity_population, @@ -1048,26 +1066,25 @@ IOResult read_input_data_county(std::vector& model, Date date, cons * * @return An IOResult indicating success or failure. */ -template +template IOResult read_input_data(std::vector& model, Date date, const std::vector& node_ids, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days, - const std::vector> immunity_population, - bool export_time_series = false) + const std::vector> immunity_population, bool export_time_series = false) { - BOOST_OUTCOME_TRY( - details::set_vaccination_data(model, path_join(pydata_dir, "vaccination_data.json"), date, node_ids, num_days)); + BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vaccination_data.json"), date, + node_ids, num_days)); // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. - BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, - scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, + scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), node_ids, - date, scaling_factor_inf, immunity_population)); - BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), node_ids, - immunity_population)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), + node_ids, date, scaling_factor_inf, immunity_population)); + BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), node_ids, + immunity_population)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -1075,7 +1092,7 @@ IOResult read_input_data(std::vector& model, Date date, const std:: // (This only represents the vectorization of the previous function over all simulation days...) log_info("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, node_ids, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "critical_cases.json"), path_join(pydata_dir, "confirmed_cases.json"), path_join(pydata_dir, "population_data.json"), immunity_population, diff --git a/cpp/models/ode_secirvvs/analyze_result.cpp b/cpp/models/ode_secirvvs/analyze_result.cpp index 74ac482a05..42b18823c3 100644 --- a/cpp/models/ode_secirvvs/analyze_result.cpp +++ b/cpp/models/ode_secirvvs/analyze_result.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele diff --git a/cpp/models/ode_secirvvs/analyze_result.h b/cpp/models/ode_secirvvs/analyze_result.h index fdc18b1a7a..2ec1617f7d 100644 --- a/cpp/models/ode_secirvvs/analyze_result.h +++ b/cpp/models/ode_secirvvs/analyze_result.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, Martin J. Kühn @@ -28,13 +28,13 @@ namespace mio namespace osecirvvs { /** - * @brief computes the p percentile of the parameters for each node. - * @param ensemble_result graph of multiple simulation runs - * @param p percentile value in open interval (0, 1) - * @return p percentile of the parameters over all runs - */ -template -std::vector ensemble_params_percentile(const std::vector>& ensemble_params, double p) + * @brief computes the p percentile of the parameters for each node. + * @param ensemble_result graph of multiple simulation runs + * @param p percentile value in open interval (0, 1) + * @return p percentile of the parameters over all runs + */ +template +std::vector ensemble_params_percentile(const std::vector>& ensemble_params, FP p) { assert(p > 0.0 && p < 1.0 && "Invalid percentile value."); @@ -42,15 +42,15 @@ std::vector ensemble_params_percentile(const std::vector>() + .parameters.template get>() .template size(); - std::vector single_element_ensemble(num_runs); + std::vector single_element_ensemble(num_runs); // lambda function that calculates the percentile of a single parameter std::vector percentile(num_nodes, Model((int)num_groups)); auto param_percentil = [&ensemble_params, p, num_runs, &percentile](auto n, auto get_param) mutable { - std::vector single_element(num_runs); + std::vector single_element(num_runs); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][n]; single_element[run] = get_param(params); @@ -61,146 +61,123 @@ std::vector ensemble_params_percentile(const std::vector>().resize(num_days); - percentile[node].parameters.template get>().resize(num_days); + percentile[node].parameters.template get>().resize(num_days); + percentile[node].parameters.template get>().resize(num_days); for (auto i = AgeGroup(0); i < AgeGroup(num_groups); i++) { //Population for (auto compart = Index(0); compart < InfectionState::Count; ++compart) { - param_percentil( - node, [ compart, i ](auto&& model) -> auto& { - return model.populations[{i, compart}]; - }); + param_percentil(node, [compart, i](auto&& model) -> auto& { + return model.populations[{i, compart}]; + }); } // times - param_percentil( - node, [i](auto&& model) -> auto& { return model.parameters.template get>()[i]; }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); //probs - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); //vaccinations - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); for (auto day = SimulationDay(0); day < num_days; ++day) { - param_percentil( - node, [ i, day ](auto&& model) -> auto& { - return model.parameters.template get>()[{i, day}]; - }); - param_percentil( - node, [ i, day ](auto&& model) -> auto& { - return model.parameters.template get>()[{i, day}]; - }); + param_percentil(node, [i, day](auto&& model) -> auto& { + return model.parameters.template get>()[{i, day}]; + }); + param_percentil(node, [i, day](auto&& model) -> auto& { + return model.parameters.template get>()[{i, day}]; + }); } //virus variants - param_percentil( - node, [i](auto&& model) -> auto& { - return model.parameters.template get>()[i]; - }); + param_percentil(node, [i](auto&& model) -> auto& { + return model.parameters.template get>()[i]; + }); } // group independent params - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { return model.parameters.template get>(); }); - param_percentil( - node, [](auto&& model) -> auto& { - return model.parameters.template get>(); - }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); + param_percentil(node, [](auto&& model) -> auto& { + return model.parameters.template get>(); + }); for (size_t run = 0; run < num_runs; run++) { auto const& params = ensemble_params[run][node]; single_element_ensemble[run] = - params.parameters.template get>() * params.populations.get_total(); + params.parameters.template get>() * params.populations.get_total(); } std::sort(single_element_ensemble.begin(), single_element_ensemble.end()); - percentile[node].parameters.template set>( + percentile[node].parameters.template set>( single_element_ensemble[static_cast(num_runs * p)]); } return percentile; diff --git a/cpp/models/ode_secirvvs/parameters_io.cpp b/cpp/models/ode_secirvvs/parameters_io.cpp index 1a43ceddb7..1d26785a8f 100644 --- a/cpp/models/ode_secirvvs/parameters_io.cpp +++ b/cpp/models/ode_secirvvs/parameters_io.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, Martin J. Kühn @@ -29,240 +29,6 @@ namespace osecirvvs { namespace details { -IOResult read_confirmed_cases_data( - std::string const& path, std::vector const& vregion, Date date, std::vector>& vnum_Exposed, - std::vector>& vnum_InfectedNoSymptoms, std::vector>& vnum_InfectedSymptoms, - std::vector>& vnum_InfectedSevere, std::vector>& vnum_icu, - std::vector>& vnum_death, std::vector>& vnum_rec, - const std::vector>& vt_Exposed, const std::vector>& vt_InfectedNoSymptoms, - const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, - const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, - const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, - const std::vector& scaling_factor_inf) -{ - BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path)); - return read_confirmed_cases_data(rki_data, vregion, date, vnum_Exposed, vnum_InfectedNoSymptoms, - vnum_InfectedSymptoms, vnum_InfectedSevere, vnum_icu, vnum_death, vnum_rec, - vt_Exposed, vt_InfectedNoSymptoms, vt_InfectedSymptoms, vt_InfectedSevere, - vt_InfectedCritical, vmu_C_R, vmu_I_H, vmu_H_U, scaling_factor_inf); -} - -IOResult read_confirmed_cases_data( - const std::vector& rki_data, std::vector const& vregion, Date date, - std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, - std::vector>& vnum_InfectedSymptoms, std::vector>& vnum_InfectedSevere, - std::vector>& vnum_icu, std::vector>& vnum_death, - std::vector>& vnum_rec, const std::vector>& vt_Exposed, - const std::vector>& vt_InfectedNoSymptoms, - const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, - const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, - const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, - const std::vector& scaling_factor_inf) -{ - auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == rki_data.end()) { - log_error("RKI data file is empty."); - return failure(StatusCode::InvalidValue, "RKI data is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("Specified date does not exist in RKI data"); - return failure(StatusCode::OutOfRange, "RKI data does not contain specified date."); - } - - // shifts the initilization to the recent past if simulation starts - // around current day and data of the future would be required. - // Only needed for preinfection compartments, exposed and InfectedNoSymptoms. - auto days_surplus = get_offset_in_days(max_date, date) - 6; // 6 > T_E + T_C - if (days_surplus > 0) { - days_surplus = 0; - } - - for (auto&& entry : rki_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { - return r == 0 || get_region_id(entry) == r; - }); - if (it != vregion.end()) { - auto region_idx = size_t(it - vregion.begin()); - - auto& t_Exposed = vt_Exposed[region_idx]; - auto& t_InfectedNoSymptoms = vt_InfectedNoSymptoms[region_idx]; - auto& t_InfectedSymptoms = vt_InfectedSymptoms[region_idx]; - auto& t_InfectedSevere = vt_InfectedSevere[region_idx]; - auto& t_InfectedCritical = vt_InfectedCritical[region_idx]; - - auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; - auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; - auto& num_rec = vnum_rec[region_idx]; - auto& num_Exposed = vnum_Exposed[region_idx]; - auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; - auto& num_death = vnum_death[region_idx]; - auto& num_icu = vnum_icu[region_idx]; - - auto& mu_C_R = vmu_C_R[region_idx]; - auto& mu_I_H = vmu_I_H[region_idx]; - auto& mu_H_U = vmu_H_U[region_idx]; - - auto age = (size_t)entry.age_group; - if (entry.date == offset_date_by_days(date, 0)) { - num_InfectedSymptoms[age] += scaling_factor_inf[age] * entry.num_confirmed; - num_rec[age] += entry.num_confirmed; - } - if (entry.date == offset_date_by_days(date, t_InfectedNoSymptoms[age] + days_surplus)) { - num_InfectedNoSymptoms[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; - num_Exposed[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; - } - if (entry.date == offset_date_by_days(date, days_surplus)) { - num_InfectedNoSymptoms[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; - } - if (entry.date == offset_date_by_days(date, t_Exposed[age] + t_InfectedNoSymptoms[age] + days_surplus)) { - num_Exposed[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; - } - if (entry.date == offset_date_by_days(date, -t_InfectedSymptoms[age])) { - num_InfectedSymptoms[age] -= scaling_factor_inf[age] * entry.num_confirmed; - num_InfectedSevere[age] += mu_I_H[age] * scaling_factor_inf[age] * entry.num_confirmed; - } - if (entry.date == offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age])) { - num_InfectedSevere[age] -= mu_I_H[age] * scaling_factor_inf[age] * entry.num_confirmed; - num_icu[age] += mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * entry.num_confirmed; - } - if (entry.date == - offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age] - t_InfectedCritical[age])) { - num_death[age] += entry.num_deaths; - num_icu[age] -= mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * entry.num_confirmed; - } - } - } - - for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { - auto region = vregion[region_idx]; - - auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; - auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; - auto& num_rec = vnum_rec[region_idx]; - auto& num_Exposed = vnum_Exposed[region_idx]; - auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; - auto& num_death = vnum_death[region_idx]; - auto& num_icu = vnum_icu[region_idx]; - - size_t num_groups = ConfirmedCasesDataEntry::age_group_names.size(); - for (size_t i = 0; i < num_groups; i++) { - // subtract infected confirmed cases which are not yet recovered - // and remove dark figure scaling factor - num_rec[i] -= num_InfectedSymptoms[i] / scaling_factor_inf[i]; - num_rec[i] -= num_InfectedSevere[i] / scaling_factor_inf[i]; - num_rec[i] -= - num_icu[i] / - scaling_factor_inf[i]; // TODO: this has to be adapted for scaling_factor_inf != 1 or != ***_icu - num_rec[i] -= num_death[i] / scaling_factor_inf[i]; - auto try_fix_constraints = [region, i](double& value, double error, auto str) { - if (value < error) { - // this should probably return a failure - // but the algorithm is not robust enough to avoid large negative - // values and there are tests that rely on it - log_error("{:s} for age group {:s} is {:.4f} for region {:d}, " - "exceeds expected negative value.", - str, ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - else if (value < 0) { - log_info("{:s} for age group {:s} is {:.4f} for region {:d}, " - "automatically corrected", - str, ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - }; - - try_fix_constraints(num_InfectedSymptoms[i], -5, "InfectedSymptoms"); - try_fix_constraints(num_InfectedNoSymptoms[i], -5, "InfectedNoSymptoms"); - try_fix_constraints(num_Exposed[i], -5, "Exposed"); - try_fix_constraints(num_InfectedSevere[i], -5, "InfectedSevere"); - try_fix_constraints(num_death[i], -5, "Dead"); - try_fix_constraints(num_icu[i], -5, "InfectedCritical"); - try_fix_constraints(num_rec[i], -20, "Recovered or vaccinated"); - } - } - - return success(); -} - -IOResult read_confirmed_cases_data_fix_recovered(std::string const& path, std::vector const& vregion, - Date date, std::vector>& vnum_rec, - double delay) -{ - BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path)); - return read_confirmed_cases_data_fix_recovered(rki_data, vregion, date, vnum_rec, delay); -} - -IOResult read_confirmed_cases_data_fix_recovered(const std::vector& rki_data, - std::vector const& vregion, Date date, - std::vector>& vnum_rec, double delay) -{ - auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == rki_data.end()) { - log_error("RKI data is empty."); - return failure(StatusCode::InvalidValue, "RKI data is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("Specified date does not exist in RKI data"); - return failure(StatusCode::OutOfRange, "RKI data does not contain specified date."); - } - - // shifts the initilization to the recent past if simulation starts - // around current day and data of the future would be required. - // Only needed for preinfection compartments, exposed and InfectedNoSymptoms. - auto days_surplus = get_offset_in_days(max_date, date) - 6; // 6 > T_E^C + T_C^I - if (days_surplus > 0) { - days_surplus = 0; - } - - for (auto&& rki_entry : rki_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&rki_entry](auto r) { - return r == 0 || get_region_id(rki_entry) == r; - }); - if (it != vregion.end()) { - auto region_idx = size_t(it - vregion.begin()); - if (rki_entry.date == offset_date_by_days(date, int(-delay))) { - vnum_rec[region_idx][size_t(rki_entry.age_group)] = rki_entry.num_confirmed; - } - } - } - - for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { - auto region = vregion[region_idx]; - auto& num_rec = vnum_rec[region_idx]; - - size_t num_groups = ConfirmedCasesDataEntry::age_group_names.size(); - for (size_t i = 0; i < num_groups; i++) { - auto try_fix_constraints = [region, i](double& value, double error, auto str) { - if (value < error) { - // this should probably return a failure - // but the algorithm is not robust enough to avoid large negative - // values and there are tests that rely on it - log_error("{:s} for age group {:s} is {:.4f} for region {:d}, " - "exceeds expected negative value.", - str, ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - else if (value < 0) { - log_info("{:s} for age group {:s} is {:.4f} for region {:d}, " - "automatically corrected", - str, ConfirmedCasesDataEntry::age_group_names[i], value, region); - value = 0.0; - } - }; - try_fix_constraints(num_rec[i], 0, "Recovered"); - } - } - - return success(); -} - } // namespace details } // namespace osecirvvs } // namespace mio diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index 6c5e2c9a37..ded65eea53 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, Martin J. Kühn @@ -40,73 +40,284 @@ namespace osecirvvs namespace details { /** - * @brief Reads subpopulations of infection states from transformed RKI cases file. - * @param[in] path Path to transformed RKI cases file. - * @param[in] vregion Vector of keys of the region of interest. - * @param[in] date Date for which the arrays are initialized. - * @param[in, out] num_* Output vector for number of people in the corresponding compartement. - * @param[in] vt_* Average time it takes to get from one compartement to another (vector with one element per age group). - * @param[in] vmu_* Probabilities to get from one compartement to another (vector with one element per age group). - * @param[in] scaling_factor_inf Factor for scaling the confirmed cases to account for estimated undetected cases. - * @see mio::read_confirmed_cases_data - * @{ - */ +* @brief Reads subpopulations of infection states from transformed RKI cases file. +* @param[in] path Path to transformed RKI cases file. +* @param[in] vregion Vector of keys of the region of interest. +* @param[in] date Date for which the arrays are initialized. +* @param[in, out] num_* Output vector for number of people in the corresponding compartement. +* @param[in] vt_* Average time it takes to get from one compartement to another (vector with one element per age group). +* @param[in] vmu_* Probabilities to get from one compartement to another (vector with one element per age group). +* @param[in] scaling_factor_inf Factor for scaling the confirmed cases to account for estimated undetected cases. +* @see mio::read_confirmed_cases_data +* @{ +*/ +template IOResult read_confirmed_cases_data( - std::string const& path, std::vector const& vregion, Date date, std::vector>& num_Exposed, - std::vector>& num_InfectedNoSymptoms, std::vector>& num_InfectedSymptoms, - std::vector>& num_InfectedSevere, std::vector>& num_icu, - std::vector>& num_death, std::vector>& num_rec, - const std::vector>& t_Exposed, const std::vector>& t_InfectedNoSymptoms, - const std::vector>& t_InfectedSymptoms, const std::vector>& t_InfectedSevere, - const std::vector>& t_InfectedCritical, const std::vector>& mu_C_R, - const std::vector>& mu_I_H, const std::vector>& mu_H_U, - const std::vector& scaling_factor_inf); + const std::vector& rki_data, std::vector const& vregion, Date date, + std::vector>& vnum_Exposed, std::vector>& vnum_InfectedNoSymptoms, + std::vector>& vnum_InfectedSymptoms, std::vector>& vnum_InfectedSevere, + std::vector>& vnum_icu, std::vector>& vnum_death, + std::vector>& vnum_rec, const std::vector>& vt_Exposed, + const std::vector>& vt_InfectedNoSymptoms, + const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, + const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, + const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, + const std::vector& scaling_factor_inf) +{ + auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { + return a.date < b.date; + }); + if (max_date_entry == rki_data.end()) { + log_error("RKI data file is empty."); + return failure(StatusCode::InvalidValue, "RKI data is empty."); + } + auto max_date = max_date_entry->date; + if (max_date < date) { + log_error("Specified date does not exist in RKI data"); + return failure(StatusCode::OutOfRange, "RKI data does not contain specified date."); + } + // shifts the initilization to the recent past if simulation starts + // around current day and data of the future would be required. + // Only needed for preinfection compartments, exposed and InfectedNoSymptoms. + auto days_surplus = get_offset_in_days(max_date, date) - 6; // 6 > T_E + T_C + if (days_surplus > 0) { + days_surplus = 0; + } + + for (auto&& entry : rki_data) { + auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { + return r == 0 || get_region_id(entry) == r; + }); + if (it != vregion.end()) { + auto region_idx = size_t(it - vregion.begin()); + + auto& t_Exposed = vt_Exposed[region_idx]; + auto& t_InfectedNoSymptoms = vt_InfectedNoSymptoms[region_idx]; + auto& t_InfectedSymptoms = vt_InfectedSymptoms[region_idx]; + auto& t_InfectedSevere = vt_InfectedSevere[region_idx]; + auto& t_InfectedCritical = vt_InfectedCritical[region_idx]; + + auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; + auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; + auto& num_rec = vnum_rec[region_idx]; + auto& num_Exposed = vnum_Exposed[region_idx]; + auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; + auto& num_death = vnum_death[region_idx]; + auto& num_icu = vnum_icu[region_idx]; + + auto& mu_C_R = vmu_C_R[region_idx]; + auto& mu_I_H = vmu_I_H[region_idx]; + auto& mu_H_U = vmu_H_U[region_idx]; + + auto age = (size_t)entry.age_group; + if (entry.date == offset_date_by_days(date, 0)) { + num_InfectedSymptoms[age] += scaling_factor_inf[age] * entry.num_confirmed; + num_rec[age] += entry.num_confirmed; + } + if (entry.date == offset_date_by_days(date, t_InfectedNoSymptoms[age] + days_surplus)) { + num_InfectedNoSymptoms[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; + num_Exposed[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; + } + if (entry.date == offset_date_by_days(date, days_surplus)) { + num_InfectedNoSymptoms[age] -= 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; + } + if (entry.date == offset_date_by_days(date, t_Exposed[age] + t_InfectedNoSymptoms[age] + days_surplus)) { + num_Exposed[age] += 1 / (1 - mu_C_R[age]) * scaling_factor_inf[age] * entry.num_confirmed; + } + if (entry.date == offset_date_by_days(date, -t_InfectedSymptoms[age])) { + num_InfectedSymptoms[age] -= scaling_factor_inf[age] * entry.num_confirmed; + num_InfectedSevere[age] += mu_I_H[age] * scaling_factor_inf[age] * entry.num_confirmed; + } + if (entry.date == offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age])) { + num_InfectedSevere[age] -= mu_I_H[age] * scaling_factor_inf[age] * entry.num_confirmed; + num_icu[age] += mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * entry.num_confirmed; + } + if (entry.date == + offset_date_by_days(date, -t_InfectedSymptoms[age] - t_InfectedSevere[age] - t_InfectedCritical[age])) { + num_death[age] += entry.num_deaths; + num_icu[age] -= mu_I_H[age] * mu_H_U[age] * scaling_factor_inf[age] * entry.num_confirmed; + } + } + } + + for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { + auto region = vregion[region_idx]; + + auto& num_InfectedNoSymptoms = vnum_InfectedNoSymptoms[region_idx]; + auto& num_InfectedSymptoms = vnum_InfectedSymptoms[region_idx]; + auto& num_rec = vnum_rec[region_idx]; + auto& num_Exposed = vnum_Exposed[region_idx]; + auto& num_InfectedSevere = vnum_InfectedSevere[region_idx]; + auto& num_death = vnum_death[region_idx]; + auto& num_icu = vnum_icu[region_idx]; + + size_t num_groups = ConfirmedCasesDataEntry::age_group_names.size(); + for (size_t i = 0; i < num_groups; i++) { + // subtract infected confirmed cases which are not yet recovered + // and remove dark figure scaling factor + num_rec[i] -= num_InfectedSymptoms[i] / scaling_factor_inf[i]; + num_rec[i] -= num_InfectedSevere[i] / scaling_factor_inf[i]; + num_rec[i] -= + num_icu[i] / + scaling_factor_inf[i]; // TODO: this has to be adapted for scaling_factor_inf != 1 or != ***_icu + num_rec[i] -= num_death[i] / scaling_factor_inf[i]; + auto try_fix_constraints = [region, i](FP& value, FP error, auto str) { + if (value < error) { + // this should probably return a failure + // but the algorithm is not robust enough to avoid large negative + // values and there are tests that rely on it + log_error("{:s} for age group {:s} is {:.4f} for region {:d}, " + "exceeds expected negative value.", + str, ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + else if (value < 0) { + log_info("{:s} for age group {:s} is {:.4f} for region {:d}, " + "automatically corrected", + str, ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + }; + + try_fix_constraints(num_InfectedSymptoms[i], -5, "InfectedSymptoms"); + try_fix_constraints(num_InfectedNoSymptoms[i], -5, "InfectedNoSymptoms"); + try_fix_constraints(num_Exposed[i], -5, "Exposed"); + try_fix_constraints(num_InfectedSevere[i], -5, "InfectedSevere"); + try_fix_constraints(num_death[i], -5, "Dead"); + try_fix_constraints(num_icu[i], -5, "InfectedCritical"); + try_fix_constraints(num_rec[i], -20, "Recovered or vaccinated"); + } + } + + return success(); +} + +template IOResult read_confirmed_cases_data( - const std::vector& rki_data, std::vector const& vregion, Date date, - std::vector>& num_Exposed, std::vector>& num_InfectedNoSymptoms, - std::vector>& num_InfectedSymptoms, std::vector>& num_InfectedSevere, - std::vector>& num_icu, std::vector>& num_death, - std::vector>& num_rec, const std::vector>& t_Exposed, - const std::vector>& t_InfectedNoSymptoms, const std::vector>& t_InfectedSymptoms, - const std::vector>& t_InfectedSevere, const std::vector>& t_InfectedCritical, - const std::vector>& mu_C_R, const std::vector>& mu_I_H, - const std::vector>& mu_H_U, const std::vector& scaling_factor_inf); + std::string const& path, std::vector const& vregion, Date date, std::vector>& vnum_Exposed, + std::vector>& vnum_InfectedNoSymptoms, std::vector>& vnum_InfectedSymptoms, + std::vector>& vnum_InfectedSevere, std::vector>& vnum_icu, + std::vector>& vnum_death, std::vector>& vnum_rec, + const std::vector>& vt_Exposed, const std::vector>& vt_InfectedNoSymptoms, + const std::vector>& vt_InfectedSymptoms, const std::vector>& vt_InfectedSevere, + const std::vector>& vt_InfectedCritical, const std::vector>& vmu_C_R, + const std::vector>& vmu_I_H, const std::vector>& vmu_H_U, + const std::vector& scaling_factor_inf) +{ + BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path)); + return read_confirmed_cases_data(rki_data, vregion, date, vnum_Exposed, vnum_InfectedNoSymptoms, + vnum_InfectedSymptoms, vnum_InfectedSevere, vnum_icu, vnum_death, vnum_rec, + vt_Exposed, vt_InfectedNoSymptoms, vt_InfectedSymptoms, vt_InfectedSevere, + vt_InfectedCritical, vmu_C_R, vmu_I_H, vmu_H_U, scaling_factor_inf); +} /**@}*/ /** - * @brief Reads confirmed cases data and translates data of day t0-delay to recovered compartment. - * @param[in] path Path to RKI confirmed cases file. - * @param[in] vregion Vector of keys of the region of interest. - * @param[in] date Date for which the arrays are initialized. - * @param[in, out] num_rec Output vector for number of people in the compartement recovered. - * @param[in] delay Number of days in the past the are used to set recovered compartment. - * @see mio::read_confirmed_cases_data - * @{ - */ +* @brief Reads confirmed cases data and translates data of day t0-delay to recovered compartment. +* @param[in] path Path to RKI confirmed cases file. +* @param[in] vregion Vector of keys of the region of interest. +* @param[in] date Date for which the arrays are initialized. +* @param[in, out] num_rec Output vector for number of people in the compartement recovered. +* @param[in] delay Number of days in the past the are used to set recovered compartment. +* @see mio::read_confirmed_cases_data +* @{ +*/ +template IOResult read_confirmed_cases_data_fix_recovered(const std::vector& rki_data, std::vector const& vregion, Date date, - std::vector>& vnum_rec, double delay = 14.); + std::vector>& vnum_rec, FP delay = 14.) +{ + auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { + return a.date < b.date; + }); + if (max_date_entry == rki_data.end()) { + log_error("RKI data is empty."); + return failure(StatusCode::InvalidValue, "RKI data is empty."); + } + auto max_date = max_date_entry->date; + if (max_date < date) { + log_error("Specified date does not exist in RKI data"); + return failure(StatusCode::OutOfRange, "RKI data does not contain specified date."); + } + + // shifts the initilization to the recent past if simulation starts + // around current day and data of the future would be required. + // Only needed for preinfection compartments, exposed and InfectedNoSymptoms. + auto days_surplus = get_offset_in_days(max_date, date) - 6; // 6 > T_E^C + T_C^I + if (days_surplus > 0) { + days_surplus = 0; + } + + for (auto&& rki_entry : rki_data) { + auto it = std::find_if(vregion.begin(), vregion.end(), [&rki_entry](auto r) { + return r == 0 || get_region_id(rki_entry) == r; + }); + if (it != vregion.end()) { + auto region_idx = size_t(it - vregion.begin()); + if (rki_entry.date == offset_date_by_days(date, int(-delay))) { + vnum_rec[region_idx][size_t(rki_entry.age_group)] = rki_entry.num_confirmed; + } + } + } + + for (size_t region_idx = 0; region_idx < vregion.size(); ++region_idx) { + auto region = vregion[region_idx]; + auto& num_rec = vnum_rec[region_idx]; + + size_t num_groups = ConfirmedCasesDataEntry::age_group_names.size(); + for (size_t i = 0; i < num_groups; i++) { + auto try_fix_constraints = [region, i](FP& value, FP error, auto str) { + if (value < error) { + // this should probably return a failure + // but the algorithm is not robust enough to avoid large negative + // values and there are tests that rely on it + log_error("{:s} for age group {:s} is {:.4f} for region {:d}, " + "exceeds expected negative value.", + str, ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + else if (value < 0) { + log_info("{:s} for age group {:s} is {:.4f} for region {:d}, " + "automatically corrected", + str, ConfirmedCasesDataEntry::age_group_names[i], value, region); + value = 0.0; + } + }; + try_fix_constraints(num_rec[i], 0, "Recovered"); + } + } + + return success(); +} + +template IOResult read_confirmed_cases_data_fix_recovered(std::string const& path, std::vector const& vregion, - Date date, std::vector>& vnum_rec, - double delay = 14.); + Date date, std::vector>& vnum_rec, + FP delay = 14.) +{ + BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path)); + return read_confirmed_cases_data_fix_recovered(rki_data, vregion, date, vnum_rec, delay); +} /**@}*/ /** - * @brief Sets the confirmed cases data for a vector of models based on input data. - * @param[in, out] model Vector of objects in which the data is set. - * @param[in] case_data Vector of case data. Each inner vector represents a different region. - * @param[in] region Vector of keys of the region of interest. - * @param[in] date Date for which the arrays are initialized. - * @param[in] scaling_factor_inf Factors by which to scale the confirmed cases of RKI data. - * @param[in] set_death If true, set the number of deaths. - */ -template + * @brief Sets the confirmed cases data for a vector of models based on input data. + * @param[in, out] model Vector of objects in which the data is set. + * @param[in] case_data Vector of case data. Each inner vector represents a different region. + * @param[in] region Vector of keys of the region of interest. + * @param[in] date Date for which the arrays are initialized. + * @param[in] scaling_factor_inf Factors by which to scale the confirmed cases of RKI data. + * @param[in] set_death If true, set the number of deaths. + */ +template IOResult set_confirmed_cases_data(std::vector& model, const std::vector& case_data, std::vector const& region, Date date, - const std::vector& scaling_factor_inf, bool set_death = false) + const std::vector& scaling_factor_inf, bool set_death = false) { + using std::round; + auto num_age_groups = (size_t)model[0].parameters.get_num_groups(); assert(scaling_factor_inf.size() == num_age_groups); //TODO: allow vector or scalar valued scaling factors assert(ConfirmedCasesDataEntry::age_group_names.size() == num_age_groups); @@ -117,53 +328,52 @@ IOResult set_confirmed_cases_data(std::vector& model, std::vector> t_InfectedSevere{model.size()}; std::vector> t_InfectedCritical{model.size()}; - std::vector> mu_C_R{model.size()}; - std::vector> mu_I_H{model.size()}; - std::vector> mu_H_U{model.size()}; + std::vector> mu_C_R{model.size()}; + std::vector> mu_I_H{model.size()}; + std::vector> mu_H_U{model.size()}; - std::vector> num_InfectedSymptoms(model.size()); - std::vector> num_death(model.size()); - std::vector> num_rec(model.size()); - std::vector> num_Exposed(model.size()); - std::vector> num_InfectedNoSymptoms(model.size()); - std::vector> num_InfectedSevere(model.size()); - std::vector> num_icu(model.size()); + std::vector> num_InfectedSymptoms(model.size()); + std::vector> num_death(model.size()); + std::vector> num_rec(model.size()); + std::vector> num_Exposed(model.size()); + std::vector> num_InfectedNoSymptoms(model.size()); + std::vector> num_InfectedSevere(model.size()); + std::vector> num_icu(model.size()); /*----------- UNVACCINATED -----------*/ for (size_t county = 0; county < model.size(); county++) { - num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); - num_death[county] = std::vector(num_age_groups, 0.0); - num_rec[county] = std::vector(num_age_groups, 0.0); - num_Exposed[county] = std::vector(num_age_groups, 0.0); - num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); - num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); - num_icu[county] = std::vector(num_age_groups, 0.0); + num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); + num_death[county] = std::vector(num_age_groups, 0.0); + num_rec[county] = std::vector(num_age_groups, 0.0); + num_Exposed[county] = std::vector(num_age_groups, 0.0); + num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); + num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); + num_icu[county] = std::vector(num_age_groups, 0.0); for (size_t group = 0; group < num_age_groups; group++) { - t_Exposed[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + t_Exposed[county].push_back( + static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedNoSymptoms[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedSymptoms[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedSevere[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedCritical[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); mu_C_R[county].push_back( - model[county].parameters.template get>()[(AgeGroup)group]); + model[county].parameters.template get>()[(AgeGroup)group]); mu_I_H[county].push_back( - model[county].parameters.template get>()[(AgeGroup)group]); - mu_H_U[county].push_back( - model[county].parameters.template get>()[(AgeGroup)group]); + model[county].parameters.template get>()[(AgeGroup)group]); + mu_H_U[county].push_back(model[county].parameters.template get>()[(AgeGroup)group]); } } - BOOST_OUTCOME_TRY(read_confirmed_cases_data(case_data, region, date, num_Exposed, num_InfectedNoSymptoms, - num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_rec, - t_Exposed, t_InfectedNoSymptoms, t_InfectedSymptoms, t_InfectedSevere, - t_InfectedCritical, mu_C_R, mu_I_H, mu_H_U, scaling_factor_inf)); + BOOST_OUTCOME_TRY(read_confirmed_cases_data( + case_data, region, date, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, + num_death, num_rec, t_Exposed, t_InfectedNoSymptoms, t_InfectedSymptoms, t_InfectedSevere, t_InfectedCritical, + mu_C_R, mu_I_H, mu_H_U, scaling_factor_inf)); for (size_t county = 0; county < model.size(); county++) { // if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) > 0) { @@ -215,47 +425,47 @@ IOResult set_confirmed_cases_data(std::vector& model, mu_I_H[county].clear(); mu_H_U[county].clear(); - num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); - num_death[county] = std::vector(num_age_groups, 0.0); - num_rec[county] = std::vector(num_age_groups, 0.0); - num_Exposed[county] = std::vector(num_age_groups, 0.0); - num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); - num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); - num_icu[county] = std::vector(num_age_groups, 0.0); + num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); + num_death[county] = std::vector(num_age_groups, 0.0); + num_rec[county] = std::vector(num_age_groups, 0.0); + num_Exposed[county] = std::vector(num_age_groups, 0.0); + num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); + num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); + num_icu[county] = std::vector(num_age_groups, 0.0); for (size_t group = 0; group < num_age_groups; group++) { - double reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; - t_Exposed[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); - t_InfectedNoSymptoms[county].push_back(static_cast(std::round( - model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); - t_InfectedSymptoms[county].push_back(static_cast(std::round( - model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + FP reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; + t_Exposed[county].push_back( + static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); + t_InfectedNoSymptoms[county].push_back(static_cast( + round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + t_InfectedSymptoms[county].push_back(static_cast( + round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); t_InfectedSevere[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedCritical[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); - double exp_fac_part_immune = - model[county].parameters.template get>()[(AgeGroup)group]; - double inf_fac_part_immune = - model[county].parameters.template get>()[(AgeGroup)group]; - double hosp_fac_part_immune = + FP exp_fac_part_immune = + model[county].parameters.template get>()[(AgeGroup)group]; + FP inf_fac_part_immune = + model[county].parameters.template get>()[(AgeGroup)group]; + FP hosp_fac_part_immune = model[county] - .parameters.template get>()[(AgeGroup)group]; - double icu_fac_part_immune = + .parameters.template get>()[(AgeGroup)group]; + FP icu_fac_part_immune = model[county] - .parameters.template get>()[(AgeGroup)group]; - mu_C_R[county].push_back(( - 1 - inf_fac_part_immune / exp_fac_part_immune * - (1 - model[county] - .parameters.template get>()[(AgeGroup)group]))); + .parameters.template get>()[(AgeGroup)group]; + mu_C_R[county].push_back( + (1 - + inf_fac_part_immune / exp_fac_part_immune * + (1 - + model[county].parameters.template get>()[(AgeGroup)group]))); mu_I_H[county].push_back( hosp_fac_part_immune / inf_fac_part_immune * - model[county].parameters.template get>()[(AgeGroup)group]); + model[county].parameters.template get>()[(AgeGroup)group]); // transfer from H to U, D unchanged. - mu_H_U[county].push_back( - icu_fac_part_immune / hosp_fac_part_immune * - model[county].parameters.template get>()[(AgeGroup)group]); + mu_H_U[county].push_back(icu_fac_part_immune / hosp_fac_part_immune * + model[county].parameters.template get>()[(AgeGroup)group]); } } @@ -303,47 +513,47 @@ IOResult set_confirmed_cases_data(std::vector& model, mu_I_H[county].clear(); mu_H_U[county].clear(); - num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); - num_death[county] = std::vector(num_age_groups, 0.0); - num_rec[county] = std::vector(num_age_groups, 0.0); - num_Exposed[county] = std::vector(num_age_groups, 0.0); - num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); - num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); - num_icu[county] = std::vector(num_age_groups, 0.0); + num_InfectedSymptoms[county] = std::vector(num_age_groups, 0.0); + num_death[county] = std::vector(num_age_groups, 0.0); + num_rec[county] = std::vector(num_age_groups, 0.0); + num_Exposed[county] = std::vector(num_age_groups, 0.0); + num_InfectedNoSymptoms[county] = std::vector(num_age_groups, 0.0); + num_InfectedSevere[county] = std::vector(num_age_groups, 0.0); + num_icu[county] = std::vector(num_age_groups, 0.0); for (size_t group = 0; group < num_age_groups; group++) { - double reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; - t_Exposed[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); - t_InfectedNoSymptoms[county].push_back(static_cast(std::round( - model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); - t_InfectedSymptoms[county].push_back(static_cast(std::round( - model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + FP reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; + t_Exposed[county].push_back( + static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); + t_InfectedNoSymptoms[county].push_back(static_cast( + round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + t_InfectedSymptoms[county].push_back(static_cast( + round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); t_InfectedSevere[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); t_InfectedCritical[county].push_back(static_cast( - std::round(model[county].parameters.template get>()[(AgeGroup)group]))); - - double reduc_immune_exp = - model[county].parameters.template get>()[(AgeGroup)group]; - double reduc_immune_inf = - model[county].parameters.template get>()[(AgeGroup)group]; - double reduc_immune_hosp = - model[county].parameters.template get>()[( - AgeGroup)group]; - double reduc_immune_icu = - model[county].parameters.template get>()[( - AgeGroup)group]; - mu_C_R[county].push_back(( - 1 - reduc_immune_inf / reduc_immune_exp * - (1 - model[county] - .parameters.template get>()[(AgeGroup)group]))); + round(model[county].parameters.template get>()[(AgeGroup)group]))); + + FP reduc_immune_exp = + model[county].parameters.template get>()[(AgeGroup)group]; + FP reduc_immune_inf = + model[county].parameters.template get>()[(AgeGroup)group]; + FP reduc_immune_hosp = + model[county] + .parameters.template get>()[(AgeGroup)group]; + FP reduc_immune_icu = + model[county] + .parameters.template get>()[(AgeGroup)group]; + mu_C_R[county].push_back( + (1 - + reduc_immune_inf / reduc_immune_exp * + (1 - + model[county].parameters.template get>()[(AgeGroup)group]))); mu_I_H[county].push_back( reduc_immune_hosp / reduc_immune_inf * - model[county].parameters.template get>()[(AgeGroup)group]); + model[county].parameters.template get>()[(AgeGroup)group]); // transfer from H to U, D unchanged. - mu_H_U[county].push_back( - reduc_immune_icu / reduc_immune_hosp * - model[county].parameters.template get>()[(AgeGroup)group]); + mu_H_U[county].push_back(reduc_immune_icu / reduc_immune_hosp * + model[county].parameters.template get>()[(AgeGroup)group]); } } @@ -381,36 +591,36 @@ IOResult set_confirmed_cases_data(std::vector& model, } /** - * @brief sets populations data from a transformed RKI cases file into a Model. - * @param[in, out] model vector of objects in which the data is set - * @param[in] path Path to transformed RKI cases file - * @param[in] region vector of keys of the region of interest - * @param[in] date Date for which the arrays are initialized - * @param[in] scaling_factor_inf factors by which to scale the confirmed cases of - * rki data - * @param set_death[in] If true, set the number of deaths. - */ -template + * @brief sets populations data from a transformed RKI cases file into a Model. + * @param[in, out] model vector of objects in which the data is set + * @param[in] path Path to transformed RKI cases file + * @param[in] region vector of keys of the region of interest + * @param[in] date Date for which the arrays are initialized + * @param[in] scaling_factor_inf factors by which to scale the confirmed cases of + * rki data + * @param set_death[in] If true, set the number of deaths. + */ +template IOResult set_confirmed_cases_data(std::vector& model, const std::string& path, std::vector const& region, Date date, - const std::vector& scaling_factor_inf, bool set_death = false) + const std::vector& scaling_factor_inf, bool set_death = false) { BOOST_OUTCOME_TRY(auto&& case_data, mio::read_confirmed_cases_data(path)); - BOOST_OUTCOME_TRY(set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf, set_death)); + BOOST_OUTCOME_TRY(set_confirmed_cases_data(model, case_data, region, date, scaling_factor_inf, set_death)); return success(); } /** - * @brief sets populations data from DIVI register into Model - * @param[in, out] model vector of objects in which the data is set - * @param[in] path Path to transformed DIVI file - * @param[in] vregion vector of keys of the regions of interest - * @param[in] date Date for which the arrays are initialized - * @param[in] scaling_factor_icu factor by which to scale the icu cases of divi data - */ -template + * @brief sets populations data from DIVI register into Model + * @param[in, out] model vector of objects in which the data is set + * @param[in] path Path to transformed DIVI file + * @param[in] vregion vector of keys of the regions of interest + * @param[in] date Date for which the arrays are initialized + * @param[in] scaling_factor_icu factor by which to scale the icu cases of divi data + */ +template IOResult set_divi_data(std::vector& model, const std::string& path, const std::vector& vregion, - Date date, double scaling_factor_icu) + Date date, FP scaling_factor_icu) { // DIVI dataset will no longer be updated from CW29 2024 on. if (!is_divi_data_available(date)) { @@ -419,19 +629,19 @@ IOResult set_divi_data(std::vector& model, const std::string& path, date); return success(); } - std::vector sum_mu_I_U(vregion.size(), 0); - std::vector> mu_I_U{model.size()}; + std::vector sum_mu_I_U(vregion.size(), 0); + std::vector> mu_I_U{model.size()}; for (size_t region = 0; region < vregion.size(); region++) { auto num_groups = model[region].parameters.get_num_groups(); for (auto i = AgeGroup(0); i < num_groups; i++) { - sum_mu_I_U[region] += model[region].parameters.template get>()[i] * - model[region].parameters.template get>()[i]; - mu_I_U[region].push_back(model[region].parameters.template get>()[i] * - model[region].parameters.template get>()[i]); + sum_mu_I_U[region] += model[region].parameters.template get>()[i] * + model[region].parameters.template get>()[i]; + mu_I_U[region].push_back(model[region].parameters.template get>()[i] * + model[region].parameters.template get>()[i]); } } - std::vector num_icu(model.size(), 0.0); - BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); + std::vector num_icu(model.size(), 0.0); + BOOST_OUTCOME_TRY(read_divi_data(path, vregion, date, num_icu)); for (size_t region = 0; region < vregion.size(); region++) { auto num_groups = model[region].parameters.get_num_groups(); @@ -452,13 +662,16 @@ IOResult set_divi_data(std::vector& model, const std::string& path, * @param[in] vregion vector of keys of the regions of interest * @param[in] date Date for which the arrays are initialized */ -template -IOResult set_population_data(std::vector& model, const std::vector>& num_population, +template +IOResult set_population_data(std::vector& model, const std::vector>& num_population, const std::vector& case_data, const std::vector& vregion, Date date) { + using std::max; + using std::min; + auto num_age_groups = ConfirmedCasesDataEntry::age_group_names.size(); - std::vector> vnum_rec(model.size(), std::vector(num_age_groups, 0.0)); + std::vector> vnum_rec(model.size(), std::vector(num_age_groups, 0.0)); BOOST_OUTCOME_TRY(read_confirmed_cases_data_fix_recovered(case_data, vregion, date, vnum_rec, 14.)); @@ -467,15 +680,15 @@ IOResult set_population_data(std::vector& model, const std::vector< auto num_groups = model[region].parameters.get_num_groups(); for (auto i = AgeGroup(0); i < num_groups; i++) { - double S_v = std::min( - model[region].parameters.template get>()[{i, SimulationDay(0)}] + - vnum_rec[region][size_t(i)], - num_population[region][size_t(i)]); - double S_pv = std::max( - model[region].parameters.template get>()[{i, SimulationDay(0)}] - - model[region].parameters.template get>()[{i, SimulationDay(0)}], + FP S_v = + min(model[region].parameters.template get>()[{i, SimulationDay(0)}] + + vnum_rec[region][size_t(i)], + num_population[region][size_t(i)]); + FP S_pv = max( + model[region].parameters.template get>()[{i, SimulationDay(0)}] - + model[region].parameters.template get>()[{i, SimulationDay(0)}], 0.0); // use std::max with 0 - double S; + FP S; if (num_population[region][size_t(i)] - S_pv - S_v < 0.0) { log_warning("Number of vaccinated persons greater than population in county {}, age group {}.", region, size_t(i)); @@ -486,32 +699,30 @@ IOResult set_population_data(std::vector& model, const std::vector< S = num_population[region][size_t(i)] - S_pv - S_v; } - double denom_E = - 1 / (S + S_pv * model[region].parameters.template get>()[i] + - S_v * model[region].parameters.template get>()[i]); - double denom_C = - 1 / (S + S_pv * model[region].parameters.template get>()[i] + - S_v * model[region].parameters.template get>()[i]); - double denom_I = + FP denom_E = + 1 / (S + S_pv * model[region].parameters.template get>()[i] + + S_v * model[region].parameters.template get>()[i]); + FP denom_C = + 1 / (S + S_pv * model[region].parameters.template get>()[i] + + S_v * model[region].parameters.template get>()[i]); + FP denom_I = 1 / - (S + - S_pv * model[region].parameters.template get>()[i] + - S_v * model[region].parameters.template get>()[i]); - double denom_HU = - 1 / - (S + - S_pv * model[region] - .parameters.template get>()[i] + - S_v * model[region] - .parameters.template get>()[i]); + (S + S_pv * model[region].parameters.template get>()[i] + + S_v * model[region].parameters.template get>()[i]); + FP denom_HU = + 1 / (S + + S_pv * model[region] + .parameters.template get>()[i] + + S_v * model[region] + .parameters.template get>()[i]); model[region].populations[{i, InfectionState::ExposedNaive}] = S * model[region].populations[{i, InfectionState::ExposedNaive}] * denom_E; model[region].populations[{i, InfectionState::ExposedPartialImmunity}] = - S_pv * model[region].parameters.template get>()[i] * + S_pv * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::ExposedPartialImmunity}] * denom_E; model[region].populations[{i, InfectionState::ExposedImprovedImmunity}] = - S_v * model[region].parameters.template get>()[i] * + S_v * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::ExposedImprovedImmunity}] * denom_E; model[region].populations[{i, InfectionState::InfectedNoSymptomsNaive}] = @@ -533,47 +744,45 @@ IOResult set_population_data(std::vector& model, const std::vector< model[region].populations[{i, InfectionState::InfectedSymptomsNaive}] = S * model[region].populations[{i, InfectionState::InfectedSymptomsNaive}] * denom_I; model[region].populations[{i, InfectionState::InfectedSymptomsPartialImmunity}] = - S_pv * model[region].parameters.template get>()[i] * + S_pv * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSymptomsPartialImmunity}] * denom_I; model[region].populations[{i, InfectionState::InfectedSymptomsImprovedImmunity}] = - S_v * model[region].parameters.template get>()[i] * + S_v * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSymptomsImprovedImmunity}] * denom_I; model[region].populations[{i, InfectionState::InfectedSymptomsNaiveConfirmed}] = S * model[region].populations[{i, InfectionState::InfectedSymptomsNaiveConfirmed}] * denom_I; model[region].populations[{i, InfectionState::InfectedSymptomsPartialImmunityConfirmed}] = - S_pv * model[region].parameters.template get>()[i] * + S_pv * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSymptomsPartialImmunityConfirmed}] * denom_I; model[region].populations[{i, InfectionState::InfectedSymptomsImprovedImmunityConfirmed}] = - S_v * model[region].parameters.template get>()[i] * + S_v * model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSymptomsImprovedImmunityConfirmed}] * denom_I; model[region].populations[{i, InfectionState::InfectedSevereNaive}] = S * model[region].populations[{i, InfectionState::InfectedSevereNaive}] * denom_HU; model[region].populations[{i, InfectionState::InfectedSeverePartialImmunity}] = S_pv * - model[region].parameters.template get>()[i] * + model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSeverePartialImmunity}] * denom_HU; model[region].populations[{i, InfectionState::InfectedSevereImprovedImmunity}] = S_v * - model[region] - .parameters.template get>()[i] * + model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedSevereImprovedImmunity}] * denom_HU; model[region].populations[{i, InfectionState::InfectedCriticalPartialImmunity}] = S_pv * - model[region].parameters.template get>()[i] * + model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedCriticalNaive}] * denom_HU; model[region].populations[{i, InfectionState::InfectedCriticalImprovedImmunity}] = S_v * - model[region] - .parameters.template get>()[i] * + model[region].parameters.template get>()[i] * model[region].populations[{i, InfectionState::InfectedCriticalNaive}] * denom_HU; model[region].populations[{i, InfectionState::InfectedCriticalNaive}] = S * model[region].populations[{i, InfectionState::InfectedCriticalNaive}] * denom_HU; model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}] = - model[region].parameters.template get>()[{i, SimulationDay(0)}] + + model[region].parameters.template get>()[{i, SimulationDay(0)}] + model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}] - (model[region].populations[{i, InfectionState::InfectedSymptomsNaive}] + model[region].populations[{i, InfectionState::InfectedSymptomsPartialImmunity}] + @@ -591,7 +800,7 @@ IOResult set_population_data(std::vector& model, const std::vector< model[region].populations[{i, InfectionState::DeadPartialImmunity}] + model[region].populations[{i, InfectionState::DeadImprovedImmunity}]); - model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}] = std::min( + model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}] = min( S_v - model[region].populations[{i, InfectionState::ExposedImprovedImmunity}] - model[region].populations[{i, InfectionState::InfectedNoSymptomsImprovedImmunity}] - model[region].populations[{i, InfectionState::InfectedNoSymptomsImprovedImmunityConfirmed}] - @@ -600,9 +809,9 @@ IOResult set_population_data(std::vector& model, const std::vector< model[region].populations[{i, InfectionState::InfectedSevereImprovedImmunity}] - model[region].populations[{i, InfectionState::InfectedCriticalImprovedImmunity}] - model[region].populations[{i, InfectionState::DeadImprovedImmunity}], - std::max(0.0, double(model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}]))); + max(0.0, FP(model[region].populations[{i, InfectionState::SusceptibleImprovedImmunity}]))); - model[region].populations[{i, InfectionState::SusceptiblePartialImmunity}] = std::max( + model[region].populations[{i, InfectionState::SusceptiblePartialImmunity}] = max( 0.0, S_pv - model[region].populations[{i, InfectionState::ExposedPartialImmunity}] - model[region].populations[{i, InfectionState::InfectedNoSymptomsPartialImmunity}] - @@ -643,20 +852,20 @@ IOResult set_population_data(std::vector& model, const std::vector< * @param[in] vregion Vector of keys of the regions of interest. * @param[in] date Date for which the arrays are initialized. */ -template +template IOResult set_population_data(std::vector& model, const std::string& path, const std::string& path_rki, const std::vector& vregion, Date date) { - BOOST_OUTCOME_TRY(auto&& num_population, read_population_data(path, vregion)); + BOOST_OUTCOME_TRY(auto&& num_population, read_population_data(path, vregion)); BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path_rki)); - BOOST_OUTCOME_TRY(set_population_data(model, num_population, rki_data, vregion, date)); + BOOST_OUTCOME_TRY(set_population_data(model, num_population, rki_data, vregion, date)); return success(); } /** * @brief Sets vaccination data for models stored in a vector. - * + * * @tparam FP Floating point type used in the Model objects. * @param[in, out] model Vector of Model objects in which the vaccination data is set. * @param[in] vacc_data Vector of VaccinationDataEntry objects containing the vaccination data. @@ -664,19 +873,21 @@ IOResult set_population_data(std::vector& model, const std::string& * @param[in] vregion Vector of region identifiers. * @param[in] num_days Number of days for which the simulation is run. */ -template +template IOResult set_vaccination_data(std::vector>& model, const std::vector& vacc_data, Date date, const std::vector& vregion, int num_days) { + using std::floor; + auto num_groups = model[0].parameters.get_num_groups(); // type conversion from UncertainValue -> FP -> int auto days_until_effective1 = static_cast( - static_cast(model[0].parameters.template get>()[AgeGroup(0)])); - auto days_until_effective2 = static_cast( - static_cast(model[0].parameters.template get>()[AgeGroup(0)])); + floor(static_cast(model[0].parameters.template get>()[AgeGroup(0)]))); + auto days_until_effective2 = static_cast(floor( + static_cast(model[0].parameters.template get>()[AgeGroup(0)]))); auto vaccination_distance = - static_cast(static_cast(model[0].parameters.template get>()[AgeGroup(0)])); + static_cast(floor(static_cast(model[0].parameters.template get>()[AgeGroup(0)]))); // iterate over regions (e.g., counties) for (size_t i = 0; i < model.size(); ++i) { @@ -805,7 +1016,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::ve /** * @brief Reads vaccination data from a file and sets it for each model. - * + * * @tparam FP Floating point type used in the Model objects. * @param[in, out] model Vector of Model objects in which the vaccination data is set. * @param[in] path Path to vaccination data file. @@ -813,7 +1024,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::ve * @param[in] vregion Vector of region identifiers. * @param[in] num_days Number of days for which the simulation is run. */ -template +template IOResult set_vaccination_data(std::vector>& model, const std::string& path, Date date, const std::vector& vregion, int num_days) { @@ -838,7 +1049,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::st return success(); } BOOST_OUTCOME_TRY(auto&& vacc_data, read_vaccination_data(path)); - BOOST_OUTCOME_TRY(set_vaccination_data(model, vacc_data, date, vregion, num_days)); + BOOST_OUTCOME_TRY(set_vaccination_data(model, vacc_data, date, vregion, num_days)); return success(); } @@ -847,7 +1058,7 @@ IOResult set_vaccination_data(std::vector>& model, const std::st #ifdef MEMILIO_HAS_HDF5 /** -* @brief Uses the initialisation method, which uses the reported data to set the initial conditions for the model for a given day. +* @brief Uses the initialisation method, which uses the reported data to set the initial conditions for the model for a given day. * The initialisation is applied for a predefined number of days and finally saved in a timeseries for each region. In the end, * we save the files "Results_rki.h5" and "Results_rki_sum.h5" in the results_dir. * Results_rki.h5 contains a time series for each region and Results_rki_sum.h5 contains the sum of all regions. @@ -863,10 +1074,10 @@ IOResult set_vaccination_data(std::vector>& model, const std::st * @param[in] population_data_path Path to population data file. * @param[in] vaccination_data_path Path to vaccination data file. */ -template +template IOResult export_input_data_county_timeseries( std::vector models, const std::string& results_dir, const std::vector& counties, Date date, - const std::vector& scaling_factor_inf, const double scaling_factor_icu, const int num_days, + const std::vector& scaling_factor_inf, const FP scaling_factor_icu, const int num_days, const std::string& divi_data_path, const std::string& confirmed_cases_path, const std::string& population_data_path, const std::string& vaccination_data_path = "") { @@ -874,11 +1085,11 @@ IOResult export_input_data_county_timeseries( assert(scaling_factor_inf.size() == num_groups); assert(num_groups == ConfirmedCasesDataEntry::age_group_names.size()); assert(models.size() == counties.size()); - std::vector> extrapolated_data( - models.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_groups)); + std::vector> extrapolated_data( + models.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_groups)); BOOST_OUTCOME_TRY(auto&& case_data, read_confirmed_cases_data(confirmed_cases_path)); - BOOST_OUTCOME_TRY(auto&& population_data, read_population_data(population_data_path, counties)); + BOOST_OUTCOME_TRY(auto&& population_data, read_population_data(population_data_path, counties)); // empty vector if set_vaccination_data is not set std::vector vacc_data; @@ -890,16 +1101,16 @@ IOResult export_input_data_county_timeseries( auto offset_day = offset_date_by_days(date, t); if (!vaccination_data_path.empty()) { - BOOST_OUTCOME_TRY(details::set_vaccination_data(models, vacc_data, offset_day, counties, num_days)); + BOOST_OUTCOME_TRY(details::set_vaccination_data(models, vacc_data, offset_day, counties, num_days)); } // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. - BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, counties, offset_day, scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_divi_data(models, divi_data_path, counties, offset_day, scaling_factor_icu)); BOOST_OUTCOME_TRY( - details::set_confirmed_cases_data(models, case_data, counties, offset_day, scaling_factor_inf, true)); - BOOST_OUTCOME_TRY(details::set_population_data(models, population_data, case_data, counties, offset_day)); + details::set_confirmed_cases_data(models, case_data, counties, offset_day, scaling_factor_inf, true)); + BOOST_OUTCOME_TRY(details::set_population_data(models, population_data, case_data, counties, offset_day)); for (size_t r = 0; r < counties.size(); r++) { extrapolated_data[r][t] = models[r].get_initial_values(); @@ -912,19 +1123,19 @@ IOResult export_input_data_county_timeseries( } } } - BOOST_OUTCOME_TRY(save_result(extrapolated_data, counties, static_cast(num_groups), - path_join(results_dir, "Results_rki.h5"))); + BOOST_OUTCOME_TRY(save_result(extrapolated_data, counties, static_cast(num_groups), + path_join(results_dir, "Results_rki.h5"))); - auto extrapolated_rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); - BOOST_OUTCOME_TRY(save_result({extrapolated_rki_data_sum[0][0]}, {0}, static_cast(num_groups), - path_join(results_dir, "Results_rki_sum.h5"))); + auto extrapolated_rki_data_sum = sum_nodes(std::vector>>{extrapolated_data}); + BOOST_OUTCOME_TRY(save_result({extrapolated_rki_data_sum[0][0]}, {0}, static_cast(num_groups), + path_join(results_dir, "Results_rki_sum.h5"))); return success(); } #else -template +template IOResult export_input_data_county_timeseries(std::vector, const std::string&, const std::vector&, - Date, const std::vector&, const double, const int, + Date, const std::vector&, const FP, const int, const std::string&, const std::string&, const std::string&, const std::string&) { @@ -936,7 +1147,7 @@ IOResult export_input_data_county_timeseries(std::vector, const std /** * Reads compartments for German counties at a specified date from data files. - * Estimates all compartments from available data using the model parameters, so the + * Estimates all compartments from available data using the model parameters, so the * model parameters must be set before calling this function. * Uses data files that contain centered 7-day moving average. * @param[in, out] model Vector of SECIRVVS models, one per county. @@ -948,24 +1159,24 @@ IOResult export_input_data_county_timeseries(std::vector, const std * @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 +template IOResult read_input_data_county(std::vector& model, Date date, const std::vector& county, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days, bool export_time_series = false) { - BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vacc_county_ageinf_ma7.json"), date, - county, num_days)); + BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vacc_county_ageinf_ma7.json"), + date, county, num_days)); // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. - BOOST_OUTCOME_TRY( - details::set_divi_data(model, path_join(pydata_dir, "county_divi_ma7.json"), county, date, scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "county_divi_ma7.json"), county, date, + scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "cases_all_county_age_ma7.json"), - county, date, scaling_factor_inf)); - BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), - path_join(pydata_dir, "cases_all_county_age_ma7.json"), county, - date)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data( + model, path_join(pydata_dir, "cases_all_county_age_ma7.json"), county, date, scaling_factor_inf)); + BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "county_current_population.json"), + path_join(pydata_dir, "cases_all_county_age_ma7.json"), county, + date)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -973,7 +1184,7 @@ IOResult read_input_data_county(std::vector& model, Date date, cons // (This only represents the vectorization of the previous function over all simulation days...) log_warning("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, county, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "county_divi_ma7.json"), path_join(pydata_dir, "cases_all_county_age_ma7.json"), path_join(pydata_dir, "county_current_population.json"), @@ -985,7 +1196,7 @@ IOResult read_input_data_county(std::vector& model, Date date, cons /** * Reads compartments for German counties at a specified date from data files. - * Estimates all compartments from available data using the model parameters, so the + * Estimates all compartments from available data using the model parameters, so the * model parameters must be set before calling this function. * Uses data files that contain centered 7-day moving average. * @param[in, out] model Vector of SECIRVVS models, one per county. @@ -997,24 +1208,24 @@ IOResult read_input_data_county(std::vector& model, Date date, cons * @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 +template IOResult read_input_data(std::vector& model, Date date, const std::vector& node_ids, - const std::vector& scaling_factor_inf, double scaling_factor_icu, + const std::vector& scaling_factor_inf, FP scaling_factor_icu, const std::string& pydata_dir, int num_days, bool export_time_series = false) { - BOOST_OUTCOME_TRY( - details::set_vaccination_data(model, path_join(pydata_dir, "vaccination_data.json"), date, node_ids, num_days)); + BOOST_OUTCOME_TRY(details::set_vaccination_data(model, path_join(pydata_dir, "vaccination_data.json"), date, + node_ids, num_days)); // TODO: Reuse more code, e.g., set_divi_data (in secir) and a set_divi_data (here) only need a different ModelType. // TODO: add option to set ICU data from confirmed cases if DIVI or other data is not available. - BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, - scaling_factor_icu)); + BOOST_OUTCOME_TRY(details::set_divi_data(model, path_join(pydata_dir, "critical_cases.json"), node_ids, date, + scaling_factor_icu)); - BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), node_ids, - date, scaling_factor_inf)); - BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), - path_join(pydata_dir, "confirmed_cases.json"), node_ids, date)); + BOOST_OUTCOME_TRY(details::set_confirmed_cases_data(model, path_join(pydata_dir, "confirmed_cases.json"), + node_ids, date, scaling_factor_inf)); + BOOST_OUTCOME_TRY(details::set_population_data(model, path_join(pydata_dir, "population_data.json"), + path_join(pydata_dir, "confirmed_cases.json"), node_ids, date)); if (export_time_series) { // Use only if extrapolated real data is needed for comparison. EXPENSIVE ! @@ -1022,7 +1233,7 @@ IOResult read_input_data(std::vector& model, Date date, const std:: // (This only represents the vectorization of the previous function over all simulation days...) log_warning("Exporting time series of extrapolated real data. This may take some minutes. " "For simulation runs over the same time period, deactivate it."); - BOOST_OUTCOME_TRY(export_input_data_county_timeseries( + BOOST_OUTCOME_TRY(export_input_data_county_timeseries( model, pydata_dir, node_ids, date, scaling_factor_inf, scaling_factor_icu, num_days, path_join(pydata_dir, "divi_data.json"), path_join(pydata_dir, "confirmed_cases.json"), path_join(pydata_dir, "population_data.json"), path_join(pydata_dir, "vaccination_data.json"))); diff --git a/cpp/tests/test_ad.cpp b/cpp/tests/test_ad.cpp index 9403ac2993..184e3b6a0e 100644 --- a/cpp/tests/test_ad.cpp +++ b/cpp/tests/test_ad.cpp @@ -24,7 +24,7 @@ #include "memilio/utils/logging.h" -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include "boost/numeric/odeint.hpp" #include diff --git a/cpp/tests/test_math_floating_point.cpp b/cpp/tests/test_math_floating_point.cpp index 3653b919f9..2ef95b862a 100644 --- a/cpp/tests/test_math_floating_point.cpp +++ b/cpp/tests/test_math_floating_point.cpp @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "ad/ad.hpp" +#include "memilio/ad/ad.h" #include "memilio/math/floating_point.h" #include diff --git a/cpp/tests/test_odesecir.cpp b/cpp/tests/test_odesecir.cpp index b59da559b1..8d8888e9bf 100755 --- a/cpp/tests/test_odesecir.cpp +++ b/cpp/tests/test_odesecir.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Daniel Abele, Martin J. Kuehn @@ -31,6 +31,8 @@ #include "memilio/math/adapt_rk.h" #include "memilio/geography/regions.h" +#include + #include TEST(TestOdeSecir, compareWithPreviousRun) @@ -50,7 +52,7 @@ TEST(TestOdeSecir, compareWithPreviousRun) mio::osecir::Model model(1); - model.parameters.set(60); + model.parameters.set>(60); model.parameters.set>(0.2); model.parameters.get>()[(mio::AgeGroup)0] = 3.2; @@ -59,9 +61,9 @@ TEST(TestOdeSecir, compareWithPreviousRun) model.parameters.get>()[(mio::AgeGroup)0] = 9.5; model.parameters.get>()[(mio::AgeGroup)0] = 7.1; - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); model.populations.set_total(nb_total_t0); model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Exposed}] = nb_exp_t0; @@ -115,7 +117,7 @@ TEST(TestOdeSecir, simulateDefault) double dt = 0.1; mio::osecir::Model model(1); - mio::TimeSeries result = simulate(t0, tmax, dt, model); + mio::TimeSeries result = mio::simulate(t0, tmax, dt, model); EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); } @@ -140,9 +142,9 @@ TEST(TestOdeSecir, checkPopulationConservation) model.parameters.get>()[(mio::AgeGroup)0] = 9.5; model.parameters.get>()[(mio::AgeGroup)0] = 7.1; - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); model.populations.set_total(nb_total_t0); model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Exposed}] = 10; @@ -168,7 +170,7 @@ TEST(TestOdeSecir, checkPopulationConservation) model.apply_constraints(); - mio::TimeSeries secir = simulate(t0, tmax, dt, model); + mio::TimeSeries secir = mio::simulate(t0, tmax, dt, model); double num_persons = 0.0; for (auto i = 0; i < secir.get_last_value().size(); i++) { @@ -192,7 +194,7 @@ TEST(TestOdeSecir, testParamConstructors) model.parameters.set>(icu_cap); - model.parameters.set(start_day); + model.parameters.set>(start_day); model.parameters.set>(seasonality); model.parameters.get>()[(mio::AgeGroup)0] = 3.2; @@ -220,15 +222,16 @@ TEST(TestOdeSecir, testParamConstructors) model.parameters.get>()[(mio::AgeGroup)0] = 0.24; model.parameters.get>()[(mio::AgeGroup)0] = 0.3; - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); mio::osecir::Model model2{model}; // copy constructor EXPECT_EQ(model.parameters.get>(), model2.parameters.get>()); - EXPECT_EQ(model.parameters.get(), model2.parameters.get()); + EXPECT_EQ(model.parameters.get>(), + model2.parameters.get>()); EXPECT_EQ(model.parameters.get>(), model2.parameters.get>()); @@ -284,7 +287,8 @@ TEST(TestOdeSecir, testParamConstructors) EXPECT_EQ(model.parameters.get>(), model3.parameters.get>()); - EXPECT_EQ(model.parameters.get(), model3.parameters.get()); + EXPECT_EQ(model.parameters.get>(), + model3.parameters.get>()); EXPECT_EQ(model.parameters.get>(), model3.parameters.get>()); @@ -339,7 +343,8 @@ TEST(TestOdeSecir, testParamConstructors) EXPECT_EQ(model4.parameters.get>(), model3.parameters.get>()); - EXPECT_EQ(model4.parameters.get(), model3.parameters.get()); + EXPECT_EQ(model4.parameters.get>(), + model3.parameters.get>()); EXPECT_EQ(model4.parameters.get>(), model3.parameters.get>()); @@ -394,7 +399,8 @@ TEST(TestOdeSecir, testParamConstructors) EXPECT_EQ(model5.parameters.get>(), model3.parameters.get>()); - EXPECT_EQ(model5.parameters.get(), model3.parameters.get()); + EXPECT_EQ(model5.parameters.get>(), + model3.parameters.get>()); EXPECT_EQ(model5.parameters.get>(), model3.parameters.get>()); @@ -490,7 +496,7 @@ TEST(TestOdeSecir, testSettersAndGetters) check_distribution(*vec[0].get_distribution(), *model.parameters.get>().get_distribution()); - model.parameters.set(vec[20]); + model.parameters.set>(vec[20]); model.parameters.set>(vec[21]); check_distribution(*vec[1].get_distribution(), @@ -571,7 +577,7 @@ TEST(TestOdeSecir, testSettersAndGetters) EXPECT_EQ(vec[17], model.parameters.get>()[(mio::AgeGroup)0]); EXPECT_EQ(vec[18], model.parameters.get>()[(mio::AgeGroup)0]); EXPECT_EQ(vec[19], model.parameters.get>()[(mio::AgeGroup)0]); - EXPECT_EQ(vec[20], model.parameters.get()); + EXPECT_EQ(vec[20], model.parameters.get>()); EXPECT_EQ(vec[21], model.parameters.get>()); } @@ -610,51 +616,59 @@ TEST(TestOdeSecir, testDamping) double nb_total_t0 = 1000, nb_inf_t0 = 10; // default model run to be compared against - mio::osecir::Model model_a(1); + mio::osecir::Model model_a(1); model_a.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedSymptoms}] = nb_inf_t0; model_a.populations.set_difference_from_total({mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}, nb_total_t0); - mio::ContactMatrixGroup& contact_matrix_a = model_a.parameters.get>(); - contact_matrix_a[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + mio::ContactMatrixGroup& contact_matrix_a = + model_a.parameters.get>(); + contact_matrix_a[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); // set probability of transmission and risk of infection to 1. model_a.parameters.get>() = 1.0; model_a.parameters.get>() = 1.0; - auto result_a = mio::simulate_flows(t0, tmax, dt, model_a, std::make_unique>()); + auto result_a = mio::simulate_flows(t0, tmax, dt, model_a, + std::make_unique>()); // reduced transmission - mio::osecir::Model model_b{model_a}; + mio::osecir::Model model_b{model_a}; model_b.populations.set_total(nb_total_t0); model_b.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedSymptoms}] = nb_inf_t0; model_b.populations.set_difference_from_total({mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}, nb_total_t0); - mio::ContactMatrixGroup& contact_matrix_b = model_b.parameters.get>(); - contact_matrix_b[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix_b[0].add_damping(0.5, mio::SimulationTime(0.)); - auto result_b = mio::simulate_flows(t0, tmax, dt, model_b, std::make_unique>()); + mio::ContactMatrixGroup& contact_matrix_b = + model_b.parameters.get>(); + contact_matrix_b[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix_b[0].add_damping(0.5, mio::SimulationTime(0.)); + auto result_b = mio::simulate_flows(t0, tmax, dt, model_b, + std::make_unique>()); EXPECT_EQ(2 * result_b[1].get_last_value()[0], result_a[1].get_last_value()[0]); // no transmission - mio::osecir::Model model_c{model_a}; + mio::osecir::Model model_c{model_a}; model_c.populations.set_total(nb_total_t0); model_c.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedSymptoms}] = nb_inf_t0; model_c.populations.set_difference_from_total({mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}, nb_total_t0); - mio::ContactMatrixGroup& contact_matrix_c = model_c.parameters.get>(); - contact_matrix_c[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix_c[0].add_damping(1., mio::SimulationTime(0.)); - auto result_c = mio::simulate_flows(t0, tmax, dt, model_c, std::make_unique>()); + mio::ContactMatrixGroup& contact_matrix_c = + model_c.parameters.get>(); + contact_matrix_c[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix_c[0].add_damping(1., mio::SimulationTime(0.)); + auto result_c = mio::simulate_flows(t0, tmax, dt, model_c, + std::make_unique>()); EXPECT_EQ(result_c[1].get_last_value()[0], 0.0); // increased transmission to a factor of two (by +1) - mio::osecir::Model model_d{model_a}; + mio::osecir::Model model_d{model_a}; model_d.populations.set_total(nb_total_t0); model_d.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedSymptoms}] = nb_inf_t0; model_d.populations.set_difference_from_total({mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}, nb_total_t0); - mio::ContactMatrixGroup& contact_matrix_d = model_d.parameters.get>(); - contact_matrix_d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); - contact_matrix_d[0].add_damping(-1., mio::SimulationTime(0.)); - auto result_d = mio::simulate_flows(t0, tmax, dt, model_d, std::make_unique>()); + mio::ContactMatrixGroup& contact_matrix_d = + model_d.parameters.get>(); + contact_matrix_d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + contact_matrix_d[0].add_damping(-1., mio::SimulationTime(0.)); + auto result_d = mio::simulate_flows(t0, tmax, dt, model_d, + std::make_unique>()); EXPECT_EQ(2 * result_a[1].get_last_value()[0], result_d[1].get_last_value()[0]); } @@ -702,12 +716,12 @@ TEST(TestOdeSecir, testModelConstraints) model.parameters.get>()[(mio::AgeGroup)0] = 0.25; model.parameters.get>()[(mio::AgeGroup)0] = 0.3; - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); model.apply_constraints(); - mio::TimeSeries secihurd = simulate(t0, tmax, dt, model); + mio::TimeSeries secihurd = mio::simulate(t0, tmax, dt, model); double max_icu_cap = 0; for (Eigen::Index i = 0; i < secihurd.get_num_time_points(); i++) { if (secihurd.get_value(i)[5] > max_icu_cap) { @@ -718,19 +732,19 @@ TEST(TestOdeSecir, testModelConstraints) mio::TimeSeries secihurd_interp = mio::interpolate_simulation_result(secihurd); // Tests that infection numbers are higher in Winter season - model.parameters.set(100); + model.parameters.set>(100); model.parameters.set>(0.5); - mio::TimeSeries secihurd_season = simulate(t0, tmax, dt, model); + mio::TimeSeries secihurd_season = mio::simulate(t0, tmax, dt, model); mio::TimeSeries secihurd_season_interp = mio::interpolate_simulation_result(secihurd_season); for (Eigen::Index i = 0; i < secihurd_interp.get_num_time_points(); i++) { EXPECT_LE(secihurd_season_interp.get_value(i)[3], secihurd_interp.get_value(i)[3]) << " at row " << i; } - model.parameters.set(280); + model.parameters.set>(280); - mio::TimeSeries secihurd_season2 = simulate(t0, tmax, dt, model); + mio::TimeSeries secihurd_season2 = mio::simulate(t0, tmax, dt, model); mio::TimeSeries secihurd_season2_interp = mio::interpolate_simulation_result(secihurd_season2); for (Eigen::Index i = 0; i < secihurd_interp.get_num_time_points(); i++) { @@ -739,11 +753,11 @@ TEST(TestOdeSecir, testModelConstraints) // temporary test for random variables set_params_distributions_normal(model, t0, tmax, 0.2); - model.parameters.set>(mio::UncertainValue(0.0)); - model.parameters.set>(mio::UncertainValue(8000)); + model.parameters.set>(mio::UncertainValue(0.0)); + model.parameters.set>(mio::UncertainValue(8000)); for (size_t j = 0; j < 10; j++) { draw_sample(model); - secihurd = simulate(t0, tmax, dt, model); + secihurd = mio::simulate(t0, tmax, dt, model); for (Eigen::Index i = 0; i < secihurd.get_num_time_points(); i++) { EXPECT_LE(secihurd.get_value(i)[5], 9000) << " at row " << i; } @@ -764,8 +778,8 @@ TEST(TestOdeSecir, testAndTraceCapacity) params.get>()[(mio::AgeGroup)0] = 2.0; params.get>()[(mio::AgeGroup)0] = 6.; - mio::ContactMatrixGroup& contact_matrix = params.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); + mio::ContactMatrixGroup& contact_matrix = params.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, cont_freq)); model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Exposed}] = nb_exp_t0; model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedNoSymptoms}] = nb_car_t0; @@ -815,7 +829,7 @@ TEST(TestOdeSecir, getInfectionsRelative) model.populations.set_difference_from_group_total( {mio::AgeGroup(2), mio::osecir::InfectionState::Susceptible}, 40'000.0); - mio::osecir::Simulation<> sim(model, 0.0); + mio::osecir::Simulation sim(model, 0.0); ASSERT_EQ(mio::osecir::get_infections_relative(sim, 0.0, sim.get_result().get_last_value()), (100. + 50. + 25.) / (10'000 + 20'000 + 40'000)); } @@ -823,12 +837,12 @@ TEST(TestOdeSecir, getInfectionsRelative) TEST(TestOdeSecir, get_reproduction_number) { const size_t num_groups = 3; - mio::osecir::Model model((int)num_groups); + mio::osecir::Model model((int)num_groups); - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(3, 3, 10)); + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(3, 3, 10)); - model.parameters.set(60); + model.parameters.set>(60); model.parameters.set>(0.2); //total population of 10.000 @@ -924,7 +938,7 @@ TEST(TestOdeSecir, get_reproduction_number) time_series1.add_time_point(0.8000000000000000000, result_5); time_series1.add_time_point(1.0, result_6); - mio::osecir::Simulation<> sim(model, 0.0); + mio::osecir::Simulation sim(model, 0.0); sim.get_result() = time_series1; EXPECT_FALSE( @@ -946,12 +960,13 @@ TEST(TestOdeSecir, get_reproduction_number) //Test handling non-invertibility of V for certain values mio::TimeSeries::Vector result_7((int)mio::osecir::InfectionState::Count * num_groups); double icu_occupancy = 0.95 * model.parameters.get>(); - double severe1 = model.parameters.get>()[(mio::AgeGroup)0] / - (model.parameters.get>()[(mio::AgeGroup)0] * 5 * - model.parameters.get>()[(mio::AgeGroup)1] * - 3.141592653589793 / (model.parameters.get>()) * - std::sin(3.141592653589793 / (0.1 * model.parameters.get>()) * - (icu_occupancy - 0.9 * model.parameters.get>()))); + double severe1 = + model.parameters.get>()[(mio::AgeGroup)0] / + (model.parameters.get>()[(mio::AgeGroup)0] * 5 * + model.parameters.get>()[(mio::AgeGroup)1] * + std::numbers::pi_v / (model.parameters.get>()) * + std::sin(std::numbers::pi_v / (0.1 * model.parameters.get>()) * + (icu_occupancy - 0.9 * model.parameters.get>()))); mio::TimeSeries time_series2((int)mio::osecir::InfectionState::Count * num_groups); result_7 << 1000, 0, 0, 0, 0, 0, severe1, 0.95 * model.parameters.get>(), 0, 0, @@ -964,13 +979,13 @@ TEST(TestOdeSecir, get_reproduction_number) //Test for small test and trace model.parameters.get>() = 0; - mio::osecir::Simulation<> sim2(model, 0.0); + mio::osecir::Simulation sim2(model, 0.0); sim2.get_result() = time_series1; EXPECT_NEAR(mio::osecir::get_reproduction_number((size_t)0, sim2).value(), 5.1941804908632792, 1e-12); // Test special domain for test-and-trace capacity/requirement: model.parameters.get>() = 1; - mio::osecir::Simulation<> sim3(model, 0.0); + mio::osecir::Simulation sim3(model, 0.0); mio::TimeSeries time_series3((int)mio::osecir::InfectionState::Count * num_groups); mio::TimeSeries::Vector result_8((int)mio::osecir::InfectionState::Count * num_groups); result_8 << 100, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0; @@ -989,16 +1004,16 @@ TEST(TestOdeSecir, get_reproduction_number) TEST(Secir, get_mobility_factors) { - auto beta = 0.25; - auto max_beta = 0.5; - auto model = mio::osecir::Model(1); - model.parameters.get>().array() = 3.; - model.parameters.get>().array() = 2.; - model.parameters.get>().array() = 0.1; - model.parameters.get>().array() = beta; + auto beta = 0.25; + auto max_beta = 0.5; + auto model = mio::osecir::Model(1); + model.parameters.get>().array() = 3.; + model.parameters.get>().array() = 2.; + model.parameters.get>().array() = 0.1; + model.parameters.get>().array() = beta; model.parameters.get>().array() = max_beta; model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedNoSymptoms}] = 100; - mio::osecir::Simulation<> sim(model, 0.0); + mio::osecir::Simulation sim(model, 0.0); { sim.get_model().parameters.get>() = 45.; auto factors = mio::osecir::get_mobility_factors(sim, 0.0, sim.get_result().get_last_value()); @@ -1023,13 +1038,13 @@ TEST(Secir, get_mobility_factors) TEST(TestOdeSecir, test_commuters) { - auto model = mio::osecir::Model(2); + auto model = mio::osecir::Model(2); auto mobility_factor = 0.1; auto non_detection_factor = 0.4; model.parameters.get_start_commuter_detection() = 0.0; model.parameters.get_end_commuter_detection() = 20.0; model.parameters.get_commuter_nondetection() = non_detection_factor; - auto sim = mio::osecir::Simulation<>(model); + auto sim = mio::osecir::Simulation(model); auto before_testing = sim.get_result().get_last_value().eval(); auto mobile_population = (sim.get_result().get_last_value() * mobility_factor).eval(); auto mobile_population_tested = mobile_population.eval(); @@ -1135,7 +1150,7 @@ TEST(TestOdeSecir, check_constraints_parameters) TEST(TestOdeSecir, apply_constraints_parameters) { - auto model = mio::osecir::Model(1); + auto model = mio::osecir::Model(1); auto indx_agegroup = mio::AgeGroup(0); const double tol_times = 1e-1; @@ -1233,7 +1248,7 @@ class ModelTestOdeSecir : public testing::Test protected: void SetUp() override { - model.parameters.set(60); + model.parameters.set>(60); model.parameters.set>(0.2); for (auto i = mio::AgeGroup(0); i < (mio::AgeGroup)num_age_groups; i++) { @@ -1313,13 +1328,13 @@ TEST_F(ModelTestOdeSecir, export_time_series_init) mio::path_join(pydata_dir_Germany, "county_current_population.json")), IsSuccess()); - auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); + auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); EXPECT_THAT(data_extrapolated, IsSuccess()); // Values were generated by the tested function export_input_data_county_timeseries; // can only check stability of the implementation, not correctness auto expected_results = - mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecir.h5")).value(); + mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecir.h5")).value(); EXPECT_THAT(print_wrap(data_extrapolated.value()[0].get_groups().matrix()), MatrixNear(print_wrap(expected_results[0].get_groups().matrix()), 1e-5, 1e-5)); @@ -1343,7 +1358,7 @@ TEST_F(ModelTestOdeSecir, export_time_series_init_old_date) mio::path_join(pydata_dir_Germany, "county_current_population.json")), IsSuccess()); - auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); + auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); EXPECT_THAT(data_extrapolated, IsSuccess()); auto results_extrapolated = data_extrapolated.value()[0].get_groups().get_value(0); @@ -1351,7 +1366,7 @@ TEST_F(ModelTestOdeSecir, export_time_series_init_old_date) // read population data std::string path = mio::path_join(pydata_dir_Germany, "county_current_population.json"); const std::vector region{1002}; - auto population_data = mio::read_population_data(path, region).value(); + auto population_data = mio::read_population_data(path, region).value(); // So, the expected values are the population data in the susceptible compartments and zeros in the other compartments. for (size_t i = 0; i < num_age_groups; i++) { @@ -1406,7 +1421,7 @@ TEST_F(ModelTestOdeSecir, model_initialization_old_date) // read population data std::string path = mio::path_join(pydata_dir_Germany, "county_current_population.json"); const std::vector region{1002}; - auto population_data = mio::read_population_data(path, region).value(); + auto population_data = mio::read_population_data(path, region).value(); // So, the expected values are the population data in the susceptible compartments and zeros in the other compartments. auto expected_values = @@ -1450,7 +1465,7 @@ TEST(TestOdeSecir, set_divi_data_empty_data) std::vector num_icu(regions.size(), 0.0); mio::Date date(2020, 4, 1); - auto result = mio::compute_divi_data(empty_data, regions, date, num_icu); + auto result = mio::compute_divi_data(empty_data, regions, date, num_icu); // Expect failure due to empty DIVI data EXPECT_FALSE(result); @@ -1511,7 +1526,7 @@ TEST(TestOdeSecir, read_population_data_failure) invalid_data.push_back(invalid_entry); // Test that read_population_data returns failure with correct message - auto result = mio::read_population_data(invalid_data, {1001}); + auto result = mio::read_population_data(invalid_data, {1001}); EXPECT_FALSE(result); EXPECT_EQ(result.error().code(), mio::StatusCode::InvalidFileFormat); EXPECT_EQ(result.error().message(), "File with county population expected."); diff --git a/cpp/tests/test_odesecirts.cpp b/cpp/tests/test_odesecirts.cpp index 5a1f72f34f..c13ca6633c 100755 --- a/cpp/tests/test_odesecirts.cpp +++ b/cpp/tests/test_odesecirts.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker @@ -108,11 +108,11 @@ const mio::osecirts::Model& osecirts_testing_model() } } - mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); - const double cont_freq = 1; - const double fact = 1.0 / (double)(size_t)nb_groups; + mio::ContactMatrixGroup& contact_matrix = model.parameters.get>(); + const double cont_freq = 1; + const double fact = 1.0 / (double)(size_t)nb_groups; contact_matrix[0] = - mio::ContactMatrix(Eigen::MatrixXd::Constant((size_t)nb_groups, (size_t)nb_groups, fact * cont_freq)); + mio::ContactMatrix(Eigen::MatrixXd::Constant((size_t)nb_groups, (size_t)nb_groups, fact * cont_freq)); return model; } @@ -141,7 +141,7 @@ TEST(TestOdeSECIRTS, get_flows) TEST(TestOdeSECIRTS, Simulation) { - auto sim = mio::osecirts::Simulation(osecirts_testing_model(), 0, 1); + auto sim = mio::osecirts::Simulation(osecirts_testing_model(), 0, 1); sim.set_integrator_core(std::make_unique>()); sim.advance(1); @@ -258,7 +258,8 @@ TEST(TestOdeSECIRTS, overflow_vaccinations) } // simulate one step with explicit Euler - auto result = mio::simulate_flows>(t0, tmax, dt, model, std::make_unique>()); + auto result = mio::simulate_flows>( + t0, tmax, dt, model, std::make_unique>()); // get the flow indices for each type of vaccination and also the indices of the susceptible compartments auto flow_indx_partial_vaccination = @@ -289,7 +290,7 @@ TEST(TestOdeSECIRTS, smooth_vaccination_rate) const ScalarType tmax = 2.; // init simple model - mio::osecirts::Model model(1); + mio::osecirts::Model model(1); auto& daily_vaccinations = model.parameters.get>(); daily_vaccinations.resize(mio::SimulationDay(static_cast(tmax + 1))); @@ -350,7 +351,7 @@ void assign_uniform_distribution(mio::UncertainValue& p, double min, dou auto invalid_initial = max == 0 ? 1.0 : max * 1.001; auto valid_initial = (max + min) * 0.5; auto initial = set_invalid_initial_value ? invalid_initial : valid_initial; - p = mio::UncertainValue(initial); + p = mio::UncertainValue(initial); p.set_distribution(mio::ParameterDistributionUniform(min, max)); } @@ -453,17 +454,17 @@ void set_contact_parameters(mio::osecirts::Model::ParameterSet& paramete auto& contact_matrix = contacts.get_cont_freq_mat(); contact_matrix[0].get_baseline().setConstant(0.5); contact_matrix[0].get_baseline().diagonal().setConstant(5.0); - contact_matrix[0].add_damping(0.3, mio::SimulationTime(5.0)); + contact_matrix[0].add_damping(0.3, mio::SimulationTime(5.0)); auto& npis = parameters.get>(); auto npi_groups = Eigen::VectorXd::Ones(contact_matrix[0].get_num_groups()); - auto npi_value = mio::UncertainValue(0.5); + auto npi_value = mio::UncertainValue(0.5); assign_uniform_distribution(npi_value, 0.25, 0.75, set_invalid_initial_value); npis.set_threshold(10.0, {mio::DampingSampling(npi_value, mio::DampingLevel(0), mio::DampingType(0), - mio::SimulationTime(0), {0}, npi_groups)}); + mio::SimulationTime(0), {0}, npi_groups)}); npis.set_base_value(100'000); - npis.set_interval(mio::SimulationTime(3.0)); - npis.set_duration(mio::SimulationTime(14.0)); + npis.set_interval(mio::SimulationTime(3.0)); + npis.set_duration(mio::SimulationTime(14.0)); parameters.get_end_dynamic_npis() = 10.0; //required for dynamic NPIs to have effect in this model } @@ -590,7 +591,7 @@ namespace mio::osecirts::Model make_model(int num_age_groups, bool set_invalid_initial_value = false) { assert(num_age_groups <= 6 && "Provide more values in functions above to test more age groups."); - mio::osecirts::Model model(num_age_groups); + mio::osecirts::Model model(num_age_groups); set_covid_parameters(model.parameters, set_invalid_initial_value); set_synthetic_population_data(model.populations, set_invalid_initial_value); set_demographic_parameters(model.parameters, set_invalid_initial_value); @@ -731,14 +732,14 @@ TEST(TestOdeSECIRTS, read_confirmed_cases) num_InfectedSevere[0] = std::vector(num_age_groups, 0.0); num_icu[0] = std::vector(num_age_groups, 0.0); - ASSERT_THAT(mio::osecirts::details::read_confirmed_cases_data( + ASSERT_THAT(mio::osecirts::details::read_confirmed_cases_data( path, region, {2020, 12, 01}, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_timm, model, std::vector(size_t(num_age_groups), 1.0), 0), IsSuccess()); // read again with invalid date - ASSERT_THAT(mio::osecirts::details::read_confirmed_cases_data( + ASSERT_THAT(mio::osecirts::details::read_confirmed_cases_data( path, region, {3020, 12, 01}, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_timm, model, std::vector(size_t(num_age_groups), 1.0), 0), @@ -746,7 +747,7 @@ TEST(TestOdeSECIRTS, read_confirmed_cases) // call the compute function with empty case data const std::vector empty_case_data; - ASSERT_THAT(mio::osecirts::details::compute_confirmed_cases_data( + ASSERT_THAT(mio::osecirts::details::compute_confirmed_cases_data( empty_case_data, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_timm, region, {2020, 12, 01}, model, std::vector(size_t(num_age_groups), 1.0), 0), @@ -761,7 +762,8 @@ TEST(TestOdeSECIRTS, set_divi_data_invalid_dates) auto model_vector = std::vector>{model}; // Test with date before DIVI dataset was available. - EXPECT_THAT(mio::osecirts::details::set_divi_data(model_vector, "", {1001}, {2019, 12, 01}, 1.0), IsSuccess()); + EXPECT_THAT(mio::osecirts::details::set_divi_data(model_vector, "", {1001}, {2019, 12, 01}, 1.0), + IsSuccess()); // Assure that populations is the same as before. EXPECT_THAT(print_wrap(model_vector[0].populations.array().cast()), MatrixNear(print_wrap(model.populations.array().cast()), 1e-10, 1e-10)); @@ -808,8 +810,8 @@ TEST(TestOdeSECIRTS, set_confirmed_cases_data_with_ICU) auto model_vector = std::vector>{model}; auto scaling_factor_inf = std::vector(size_t(model.parameters.get_num_groups()), 1.0); - EXPECT_THAT(mio::osecirts::details::set_confirmed_cases_data(model_vector, case_data, {1002}, mid_day, - scaling_factor_inf, immunity_population), + EXPECT_THAT(mio::osecirts::details::set_confirmed_cases_data(model_vector, case_data, {1002}, mid_day, + scaling_factor_inf, immunity_population), IsSuccess()); // Since, TimeInfectedCritical is 1, the number of ICU cases is the difference of confirmed cases between two days, which is 1. @@ -1019,13 +1021,14 @@ TEST(TestOdeSECIRTS, export_time_series_init) mio::path_join(TEST_DATA_DIR, "vacc_county_ageinf_ma7.json")), IsSuccess()); - auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); + auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); ASSERT_THAT(data_extrapolated, IsSuccess()); // Values were generated by the tested function export_input_data_county_timeseries; // can only check stability of the implementation, not correctness auto expected_results = - mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecirts.h5")).value(); + mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecirts.h5")) + .value(); ASSERT_THAT(print_wrap(data_extrapolated.value()[0].get_groups().matrix()), MatrixNear(print_wrap(expected_results[0].get_groups().matrix()), 1e-5, 1e-5)); @@ -1100,8 +1103,8 @@ TEST(TestOdeSECIRTS, set_vaccination_data_not_avail) std::vector region = {1001}; std::string any_path = "dummy_vacc_path.json"; - auto result = - mio::osecirts::details::set_vaccination_data(model_vector, any_path, unavailable_date, region, num_days); + auto result = mio::osecirts::details::set_vaccination_data(model_vector, any_path, unavailable_date, region, + num_days); ASSERT_THAT(result, IsSuccess()); @@ -1137,7 +1140,8 @@ TEST(TestOdeSECIRTS, set_vaccination_data_min_date_not_avail) mio::Date earlier_date(2021, 1, 1); std::vector region = {0}; - auto result = mio::osecirts::details::set_vaccination_data(model_vector, vacc_data, earlier_date, region, num_days); + auto result = + mio::osecirts::details::set_vaccination_data(model_vector, vacc_data, earlier_date, region, num_days); ASSERT_THAT(result, IsSuccess()); // check that vaccinations are set to zero for all days and age groups @@ -1242,7 +1246,7 @@ TEST(TestOdeSECIRTS, test_commuters) model.parameters.get_start_commuter_detection() = 0.0; model.parameters.get_end_commuter_detection() = 20.0; model.parameters.get_commuter_nondetection() = non_detection_factor; - auto sim = mio::osecirts::Simulation<>(model); + auto sim = mio::osecirts::Simulation(model); auto before_testing = sim.get_result().get_last_value().eval(); auto migrated = (sim.get_result().get_last_value() * migration_factor).eval(); auto migrated_tested = migrated.eval(); @@ -1454,7 +1458,7 @@ TEST(TestOdeSECIRTS, check_constraints_parameters) TEST(TestOdeSECIRTS, apply_constraints_parameters) { const double tol_times = 1e-1; - auto model = mio::osecirts::Model(1); + auto model = mio::osecirts::Model(1); auto indx_agegroup = mio::AgeGroup(0); EXPECT_EQ(model.parameters.apply_constraints(), 0); @@ -1601,8 +1605,8 @@ TEST(TestOdeSECIRTS, apply_variant_function) auto model = mio::osecirts::Model(1); model.parameters.set>(0.2); - model.parameters.set(0); - model.parameters.set(10); + model.parameters.set>(0); + model.parameters.set>(10); model.parameters.set>(2.0); // set vaccinations diff --git a/cpp/tests/test_odesecirvvs.cpp b/cpp/tests/test_odesecirvvs.cpp index 0ca44387c4..548861480d 100755 --- a/cpp/tests/test_odesecirvvs.cpp +++ b/cpp/tests/test_odesecirvvs.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Daniel Abele @@ -65,7 +65,7 @@ TEST(TestOdeSECIRVVS, simulateDefault) model.parameters.get>().array().setConstant(0); model.parameters.get>().resize(mio::SimulationDay(size_t(1000))); model.parameters.get>().array().setConstant(0); - mio::TimeSeries result = simulate(t0, tmax, dt, model); + mio::TimeSeries result = mio::simulate(t0, tmax, dt, model); EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); } @@ -122,8 +122,8 @@ TEST(TestOdeSECIRVVS, reduceToSecirAndCompareWithPreviousRun) auto& contacts = model.parameters.get>(); auto& contact_matrix = contacts.get_cont_freq_mat(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); - contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(30.)); //times model.parameters.get>()[mio::AgeGroup(0)] = 3.2; @@ -163,7 +163,7 @@ TEST(TestOdeSECIRVVS, reduceToSecirAndCompareWithPreviousRun) // integrator->set_dt_max(1.0); // integrator->set_rel_tolerance(1e-4); // integrator->set_abs_tolerance(1e-1); - // mio::TimeSeries secihurd = simulate(t0, tmax, 0.1, model, std::move(integrator)); + // mio::TimeSeries secihurd = mio::simulate(t0, tmax, 0.1, model, std::move(integrator)); // auto compare = load_test_data_csv("secihurd-compare.csv"); @@ -282,17 +282,17 @@ void set_contact_parameters(mio::osecirvvs::Model::ParameterSet& paramet auto& contact_matrix = contacts.get_cont_freq_mat(); contact_matrix[0].get_baseline().setConstant(0.5); contact_matrix[0].get_baseline().diagonal().setConstant(5.0); - contact_matrix[0].add_damping(0.3, mio::SimulationTime(5.0)); + contact_matrix[0].add_damping(0.3, mio::SimulationTime(5.0)); auto& npis = parameters.get>(); auto npi_groups = Eigen::VectorXd::Ones(contact_matrix[0].get_num_groups()); auto npi_value = mio::UncertainValue(0.5); assign_uniform_distribution(npi_value, 0.25, 0.75, set_invalid_initial_value); npis.set_threshold(10.0, {mio::DampingSampling(npi_value, mio::DampingLevel(0), mio::DampingType(0), - mio::SimulationTime(0), {0}, npi_groups)}); + mio::SimulationTime(0), {0}, npi_groups)}); npis.set_base_value(100'000); - npis.set_interval(mio::SimulationTime(3.0)); - npis.set_duration(mio::SimulationTime(14.0)); + npis.set_interval(mio::SimulationTime(3.0)); + npis.set_duration(mio::SimulationTime(14.0)); parameters.get_end_dynamic_npis() = 10.0; //required for dynamic NPIs to have effect in this model parameters.template get>() = 7; } @@ -563,7 +563,7 @@ TEST(TestOdeSECIRVVS, read_confirmed_cases) model[0].parameters.template get>()[(mio::AgeGroup)group]); } - auto read = mio::osecirvvs::details::read_confirmed_cases_data( + auto read = mio::osecirvvs::details::read_confirmed_cases_data( path, region, {2020, 12, 01}, num_Exposed, num_InfectedNoSymptoms, num_InfectedSymptoms, num_InfectedSevere, num_icu, num_death, num_rec, t_Exposed, t_InfectedNoSymptoms, t_InfectedSymptoms, t_InfectedSevere, t_InfectedCritical, mu_C_R, mu_I_H, mu_H_U, std::vector(size_t(num_age_groups), 1.0)); @@ -579,7 +579,8 @@ TEST(TestOdeSECIRVVS, set_divi_data_invalid_dates) auto model_vector = std::vector>{model}; // Test with date before DIVI dataset was available. - EXPECT_THAT(mio::osecirvvs::details::set_divi_data(model_vector, "", {1001}, {2019, 12, 01}, 1.0), IsSuccess()); + EXPECT_THAT(mio::osecirvvs::details::set_divi_data(model_vector, "", {1001}, {2019, 12, 01}, 1.0), + IsSuccess()); // Assure that populations is the same as before. EXPECT_THAT(print_wrap(model_vector[0].populations.array().cast()), MatrixNear(print_wrap(model.populations.array().cast()), 1e-10, 1e-10)); @@ -622,9 +623,9 @@ TEST(TestOdeSECIRVVS, set_confirmed_cases_data_with_ICU) // calculate ICU values using set_confirmed_cases_data auto model_vector = std::vector>{model}; auto scaling_factor_inf = std::vector(size_t(model.parameters.get_num_groups()), 1.0); - EXPECT_THAT( - mio::osecirvvs::details::set_confirmed_cases_data(model_vector, case_data, {1002}, mid_day, scaling_factor_inf), - IsSuccess()); + EXPECT_THAT(mio::osecirvvs::details::set_confirmed_cases_data(model_vector, case_data, {1002}, mid_day, + scaling_factor_inf), + IsSuccess()); // Since, TimeInfectedCritical is 1, the number of ICU cases is the difference of confirmed cases between two days, which is 1. // We only have an entry for age group 2. All other age groups should be zero. @@ -830,13 +831,14 @@ TEST(TestOdeSECIRVVS, export_time_series_init) mio::path_join(TEST_DATA_DIR, "vacc_county_ageinf_ma7.json")), IsSuccess()); - auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); + auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); ASSERT_THAT(data_extrapolated, IsSuccess()); // Values were generated by the tested function export_input_data_county_timeseries; // can only check stability of the implementation, not correctness auto expected_results = - mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecirvvs.h5")).value(); + mio::read_result(mio::path_join(TEST_DATA_DIR, "export_time_series_initialization_osecirvvs.h5")) + .value(); ASSERT_THAT(print_wrap(data_extrapolated.value()[0].get_groups().matrix()), MatrixNear(print_wrap(expected_results[0].get_groups().matrix()), 1e-5, 1e-5)); @@ -868,7 +870,7 @@ TEST(TestOdeSECIRVVS, export_time_series_init_old_date) mio::path_join(TEST_DATA_DIR, "vacc_county_ageinf_ma7.json")), IsSuccess()); - auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); + auto data_extrapolated = mio::read_result(mio::path_join(tmp_results_dir, "Results_rki.h5")); ASSERT_THAT(data_extrapolated, IsSuccess()); auto results_extrapolated = data_extrapolated.value()[0].get_groups().get_value(0); @@ -876,7 +878,7 @@ TEST(TestOdeSECIRVVS, export_time_series_init_old_date) // read population data std::string path = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{0}; - auto population_data = mio::read_population_data(path, region).value(); + auto population_data = mio::read_population_data(path, region).value(); // So, the expected values are the population data in the susceptible compartments and zeros in the other compartments. for (auto i = 0; i < num_age_groups; i++) { @@ -947,7 +949,7 @@ TEST(TestOdeSECIRVVS, model_initialization_old_date) // read population data std::string path = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{0}; - auto population_data = mio::read_population_data(path, region).value(); + auto population_data = mio::read_population_data(path, region).value(); // So, the expected values are the population data in the susceptible compartments and zeros in the other compartments. for (auto i = 0; i < num_age_groups; i++) { @@ -984,7 +986,7 @@ TEST(TestOdeSECIRVVS, model_initialization_old_date_county) // read population data std::string path = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{0}; - auto population_data = mio::read_population_data(path, region).value(); + auto population_data = mio::read_population_data(path, region).value(); // So, the expected values are the population data in the susceptible compartments and zeros in the other compartments. for (auto i = 0; i < num_age_groups; i++) { @@ -1017,10 +1019,10 @@ TEST(TestOdeSECIRVVS, set_population_data_overflow_vacc) std::string path_pop_data = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{0}; - auto population_data = mio::read_population_data(path_pop_data, region).value(); + auto population_data = mio::read_population_data(path_pop_data, region).value(); // we choose the date so that no case data is available - ASSERT_THAT(mio::osecirvvs::details::set_population_data( + ASSERT_THAT(mio::osecirvvs::details::set_population_data( model_vector, path_pop_data, mio::path_join(TEST_DATA_DIR, "cases_all_county_age_ma7.json"), {0}, {1000, 12, 01}), IsSuccess()); @@ -1057,10 +1059,10 @@ TEST(TestOdeSECIRVVS, set_population_data_no_data_avail) std::string path_pop_data = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{0}; - auto population_data = mio::read_population_data(path_pop_data, region).value(); + auto population_data = mio::read_population_data(path_pop_data, region).value(); // we choose the date so that no case data is available - ASSERT_THAT(mio::osecirvvs::details::set_population_data( + ASSERT_THAT(mio::osecirvvs::details::set_population_data( model_vector, path_pop_data, mio::path_join(TEST_DATA_DIR, "cases_all_county_age_ma7.json"), {200}, {1000, 12, 01}), IsSuccess()); @@ -1079,7 +1081,7 @@ TEST(TestOdeSECIRVVS, run_simulation) // Load result of a previous run; only tests stability, not correctness. auto expected_result = - mio::read_result(mio::path_join(TEST_DATA_DIR, "results_osecirvvs.h5")).value()[0].get_groups(); + mio::read_result(mio::path_join(TEST_DATA_DIR, "results_osecirvvs.h5")).value()[0].get_groups(); ASSERT_THAT(print_wrap(result.matrix()), MatrixNear(print_wrap(expected_result.matrix()), 1e-5, 1e-5)); } @@ -1110,8 +1112,8 @@ TEST(TestOdeSECIRVVS, set_vaccination_data_not_avail) mio::Date unavailable_date(2019, 1, 1); // Date before vaccinations started std::vector region = {1001}; std::string any_path = "dummy_vacc_path.json"; - auto result = - mio::osecirvvs::details::set_vaccination_data(model_vector, any_path, unavailable_date, region, num_days); + auto result = mio::osecirvvs::details::set_vaccination_data(model_vector, any_path, unavailable_date, + region, num_days); ASSERT_THAT(result, IsSuccess()); @@ -1149,7 +1151,7 @@ TEST(TestOdeSECIRVVS, set_vaccination_data_min_date_not_avail) std::vector region = {0}; auto result = - mio::osecirvvs::details::set_vaccination_data(model_vector, vacc_data, earlier_date, region, num_days); + mio::osecirvvs::details::set_vaccination_data(model_vector, vacc_data, earlier_date, region, num_days); ASSERT_THAT(result, IsSuccess()); // check that vaccinations are set to zero for all days and age groups @@ -1233,7 +1235,7 @@ TEST(TestOdeSECIRVVS, get_mobility_factors) { auto num_age_groups = 2; auto model = make_model(num_age_groups); - auto sim = mio::osecirvvs::Simulation<>(model); + auto sim = mio::osecirvvs::Simulation(model); auto y = sim.get_result()[0]; auto mobility_factors = mio::osecirvvs::get_mobility_factors(sim, 0.0, y); @@ -1253,7 +1255,7 @@ TEST(TestOdeSECIRVVS, test_commuters) model.parameters.get_start_commuter_detection() = 0.0; model.parameters.get_end_commuter_detection() = 20.0; model.parameters.get_commuter_nondetection() = non_detection_factor; - auto sim = mio::osecirvvs::Simulation<>(model); + auto sim = mio::osecirvvs::Simulation(model); auto before_testing = sim.get_result().get_last_value().eval(); auto mobile_population = (sim.get_result().get_last_value() * mobility_factor).eval(); auto mobile_population_tested = mobile_population.eval(); @@ -1479,7 +1481,7 @@ TEST(TestOdeSECIRVVS, check_constraints_parameters) TEST(TestOdeSECIRVVS, apply_constraints_parameters) { const double tol_times = 1e-1; - auto model = mio::osecirvvs::Model(1); + auto model = mio::osecirvvs::Model(1); auto indx_agegroup = mio::AgeGroup(0); EXPECT_EQ(model.parameters.apply_constraints(), 0); @@ -1612,13 +1614,13 @@ TEST(TestOdeSECIRVVS, apply_constraints_parameters) TEST(TestOdeSECIRVVS, apply_variant_function) { - auto model = mio::osecirvvs::Model(1); + auto model = mio::osecirvvs::Model(1); model.parameters.set>(0.2); - model.parameters.set(0); - model.parameters.set(10); + model.parameters.set>(0); + model.parameters.set>(10); model.parameters.set>(2.0); - auto sim = mio::osecirvvs::Simulation<>(model); + auto sim = mio::osecirvvs::Simulation(model); // test that the transmission probability is not changed due to calling the advance function sim.advance(0.01); From e4ab064d399cc32454022c58a2e05c6ba4958ad0 Mon Sep 17 00:00:00 2001 From: julianlitz Date: Tue, 9 Sep 2025 10:06:08 +0000 Subject: [PATCH 2/3] data folder --- cpp/memilio/data/analyze_result.cpp | 175 +-------------------- cpp/memilio/data/analyze_result.h | 227 +++++++++++++++++++++++++--- 2 files changed, 208 insertions(+), 194 deletions(-) diff --git a/cpp/memilio/data/analyze_result.cpp b/cpp/memilio/data/analyze_result.cpp index 3b3b25fa69..e6a45dd81e 100644 --- a/cpp/memilio/data/analyze_result.cpp +++ b/cpp/memilio/data/analyze_result.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, David Kerkmann, Sascha Korf @@ -25,177 +25,4 @@ namespace mio { -TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, const double abs_tol) -{ - const auto t0 = simulation_result.get_time(0); - const auto t_max = simulation_result.get_last_time(); - // add another day if the first time point is equal to day_0 up to absolute tolerance tol - const auto day0 = (t0 - abs_tol < std::ceil(t0) - 1) ? std::floor(t0) : std::ceil(t0); - // add another day if the last time point is equal to day_max up to absolute tolerance tol - const auto day_max = (t_max + abs_tol > std::floor(t_max) + 1) ? std::ceil(t_max) : std::floor(t_max); - - // create interpolation_times vector with all days between day0 and day_max - std::vector tps(static_cast(day_max) - static_cast(day0) + 1); - std::iota(tps.begin(), tps.end(), day0); - - return interpolate_simulation_result(simulation_result, tps); -} - -TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, - const std::vector& interpolation_times) -{ - assert(simulation_result.get_num_time_points() > 0 && "TimeSeries must not be empty."); - - assert(std::is_sorted(interpolation_times.begin(), interpolation_times.end()) && - "Time points for interpolation have to be sorted in non-descending order."); - - if (interpolation_times.size() >= 2) { - assert((interpolation_times[1] > simulation_result.get_time(0) && - interpolation_times.rbegin()[1] <= simulation_result.get_last_time()) && - "All but the first and the last time point of interpolation have lie between simulation times (strictly " - "for lower boundary)."); - } - - TimeSeries interpolated(simulation_result.get_num_elements()); - - if (interpolation_times.size() == 0) { - return interpolated; - } - - size_t interp_idx = 0; - // add first time point of interpolation times in case it is smaller than the first time point of simulation_result - // this is used for the case that it equals the first time point of simulation up to tolerance - // this is necessary even if the tolerance is 0 due to the way the comparison in the loop is implemented (< and >=) - if (simulation_result.get_time(0) >= interpolation_times[0]) { - interpolated.add_time_point(interpolation_times[0], simulation_result[0]); - ++interp_idx; - } - - //interpolate between pair of time points that lie on either side of each interpolation point - for (Eigen::Index sim_idx = 0; - sim_idx < simulation_result.get_num_time_points() - 1 && interp_idx < interpolation_times.size();) { - //only go to next pair of time points if no time point is added. - //otherwise check the same time points again - //in case there is more than one interpolation point between the two time points - if (simulation_result.get_time(sim_idx) < interpolation_times[interp_idx] && - simulation_result.get_time(sim_idx + 1) >= interpolation_times[interp_idx]) { - interpolated.add_time_point( - interpolation_times[interp_idx], - linear_interpolation(interpolation_times[interp_idx], simulation_result.get_time(sim_idx), - simulation_result.get_time(sim_idx + 1), simulation_result[sim_idx], - simulation_result[sim_idx + 1])); - ++interp_idx; - } - else { - ++sim_idx; - } - } - - // add last time point of interpolation times in case it is larger than the last time point of simulation_result - // this is used for the case that it equals the last time point of simulation up to tolerance - if (interp_idx < interpolation_times.size() && - simulation_result.get_last_time() < interpolation_times[interp_idx]) { - interpolated.add_time_point(interpolation_times[interp_idx], simulation_result.get_last_value()); - } - - return interpolated; -} - -std::vector>> -sum_nodes(const std::vector>>& ensemble_result) -{ - auto num_runs = ensemble_result.size(); - auto num_nodes = ensemble_result[0].size(); - auto num_time_points = ensemble_result[0][0].get_num_time_points(); - auto num_elements = ensemble_result[0][0].get_num_elements(); - - std::vector>> sum_result( - num_runs, std::vector>(1, TimeSeries::zero(num_time_points, num_elements))); - - for (size_t run = 0; run < num_runs; run++) { - for (Eigen::Index time = 0; time < num_time_points; time++) { - sum_result[run][0].get_time(time) = ensemble_result[run][0].get_time(time); - for (size_t node = 0; node < num_nodes; node++) { - sum_result[run][0][time] += ensemble_result[run][node][time]; - } - } - } - return sum_result; -} - -std::vector> ensemble_mean(const std::vector>>& ensemble_result) -{ - auto num_runs = ensemble_result.size(); - auto num_nodes = ensemble_result[0].size(); - auto num_time_points = ensemble_result[0][0].get_num_time_points(); - auto num_elements = ensemble_result[0][0].get_num_elements(); - - std::vector> mean(num_nodes, TimeSeries::zero(num_time_points, num_elements)); - - for (size_t run = 0; run < num_runs; run++) { - assert(ensemble_result[run].size() == num_nodes && "ensemble results not uniform."); - for (size_t node = 0; node < num_nodes; node++) { - assert(ensemble_result[run][node].get_num_time_points() == num_time_points && - "ensemble results not uniform."); - for (Eigen::Index time = 0; time < num_time_points; time++) { - assert(ensemble_result[run][node].get_num_elements() == num_elements && - "ensemble results not uniform."); - mean[node].get_time(time) = ensemble_result[run][node].get_time(time); - mean[node][time] += ensemble_result[run][node][time] / num_runs; - } - } - } - - return mean; -} - -std::vector> ensemble_percentile(const std::vector>>& ensemble_result, - double p) -{ - assert(p > 0.0 && p < 1.0 && "Invalid percentile value."); - - auto num_runs = ensemble_result.size(); - auto num_nodes = ensemble_result[0].size(); - auto num_time_points = ensemble_result[0][0].get_num_time_points(); - auto num_elements = ensemble_result[0][0].get_num_elements(); - - std::vector> percentile(num_nodes, TimeSeries::zero(num_time_points, num_elements)); - - std::vector single_element_ensemble(num_runs); //reused for each element - for (size_t node = 0; node < num_nodes; node++) { - for (Eigen::Index time = 0; time < num_time_points; time++) { - percentile[node].get_time(time) = ensemble_result[0][node].get_time(time); - for (Eigen::Index elem = 0; elem < num_elements; elem++) { - std::transform(ensemble_result.begin(), ensemble_result.end(), single_element_ensemble.begin(), - [=](auto& run) { - return run[node][time][elem]; - }); - std::sort(single_element_ensemble.begin(), single_element_ensemble.end()); - percentile[node][time][elem] = single_element_ensemble[static_cast(num_runs * p)]; - } - } - } - return percentile; -} - -double result_distance_2norm(const std::vector>& result1, - const std::vector>& result2) -{ - assert(result1.size() == result2.size()); - assert(result1.size() > 0); - assert(result1[0].get_num_time_points() > 0); - assert(result1[0].get_num_elements() > 0); - - auto norm_sqr = 0.0; - for (auto iter_node1 = result1.begin(), iter_node2 = result2.begin(); iter_node1 < result1.end(); - ++iter_node1, ++iter_node2) { - for (Eigen::Index time_idx = 0; time_idx < iter_node1->get_num_time_points(); ++time_idx) { - auto v1 = (*iter_node1)[time_idx]; - auto v2 = (*iter_node2)[time_idx]; - norm_sqr += ((v1 - v2).array() * (v1 - v2).array()).sum(); - } - } - return std::sqrt(norm_sqr); -} - } // namespace mio diff --git a/cpp/memilio/data/analyze_result.h b/cpp/memilio/data/analyze_result.h index 0377a14fbb..5e80a05773 100644 --- a/cpp/memilio/data/analyze_result.h +++ b/cpp/memilio/data/analyze_result.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2020-2025 MEmilio * * Authors: Wadim Koslow, Daniel Abele, David Kerkmann, Sascha Korf @@ -22,6 +22,7 @@ #include "memilio/utils/time_series.h" #include "memilio/mobility/metapopulation_mobility_instant.h" +#include "memilio/math/interpolation.h" #include "memilio/io/io.h" #include @@ -40,8 +41,8 @@ namespace mio * @param abs_tol absolute tolerance given for doubles t0 and tmax to account for small deviations from whole days. * @return interpolated time series */ -TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, - const double abs_tol = 1e-14); +template +TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, const FP abs_tol = 1e-14); /** * @brief interpolate time series with freely chosen time points that lie in between the time points of the given time series up to a given tolerance. @@ -50,8 +51,10 @@ TimeSeries interpolate_simulation_result(const TimeSeries& simul * @param interpolations_times std::vector of time points at which simulation results are interpolated. * @return interpolated time series at given interpolation points */ -TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, - const std::vector& interpolation_times); +template +TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, + + const std::vector& interpolation_times); /** * helper template, type returned by overload interpolate_simulation_result(T t) @@ -76,8 +79,8 @@ std::vector> interpolate_ensemble_results(const std::vecto return interpolated; } -std::vector>> -sum_nodes(const std::vector>>& ensemble_result); +template +std::vector>> sum_nodes(const std::vector>>& ensemble_result); /** * @brief computes mean of each compartment, node, and time point over all runs @@ -87,7 +90,8 @@ sum_nodes(const std::vector>>& ensemble_result); * @param ensemble_results uniform results of multiple simulation runs * @return mean of the results over all runs */ -std::vector> ensemble_mean(const std::vector>>& ensemble_results); +template +std::vector> ensemble_mean(const std::vector>>& ensemble_results); /** * @brief computes the p percentile of the result for each compartment, node, and time point. @@ -100,19 +104,19 @@ std::vector> ensemble_mean(const std::vector> ensemble_percentile(const std::vector>>& ensemble_result, - double p); +template +std::vector> ensemble_percentile(const std::vector>>& ensemble_result, FP p); /** * interpolate time series with evenly spaced, integer time points for each node. * @see interpolate_simulation_result * @param graph_result graph of simulations whose results will be interpolated * @return one interpolated time series per node */ -template -std::vector> -interpolate_simulation_result(const Graph, MobilityEdge>& graph_result) +template +std::vector> +interpolate_simulation_result(const Graph, MobilityEdge>& graph_result) { - std::vector> interpolated; + std::vector> interpolated; interpolated.reserve(graph_result.nodes().size()); std::transform(graph_result.nodes().begin(), graph_result.nodes().end(), std::back_inserter(interpolated), [](auto& n) { @@ -129,8 +133,9 @@ interpolate_simulation_result(const Graph, MobilityEd * @param result2 second result. * @return Computed distance between result1 and result2. */ -double result_distance_2norm(const std::vector>& result1, - const std::vector>& result2); +template +FP result_distance_2norm(const std::vector>& result1, + const std::vector>& result2); /** * Compute the distance between two compartment model simulation results in one compartment. @@ -142,10 +147,12 @@ double result_distance_2norm(const std::vector>& result1 * @param compartment the compartment to compare. * @return Computed distance between result1 and result2. */ -template -double result_distance_2norm(const std::vector>& result1, - const std::vector>& result2, InfectionState compartment) +template +FP result_distance_2norm(const std::vector>& result1, + const std::vector>& result2, InfectionState compartment) { + using std::sqrt; + assert(result1.size() == result2.size()); assert(result1.size() > 0); assert(result1[0].get_num_time_points() > 0); @@ -167,7 +174,187 @@ double result_distance_2norm(const std::vector>& result1 } } } - return std::sqrt(norm_sqr); + return sqrt(norm_sqr); +} + +template +TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, const FP abs_tol) +{ + using std::ceil; + using std::floor; + const auto t0 = simulation_result.get_time(0); + const auto t_max = simulation_result.get_last_time(); + // add another day if the first time point is equal to day_0 up to absolute tolerance tol + const auto day0 = (t0 - abs_tol < ceil(t0) - 1) ? floor(t0) : ceil(t0); + // add another day if the last time point is equal to day_max up to absolute tolerance tol + const auto day_max = (t_max + abs_tol > floor(t_max) + 1) ? ceil(t_max) : floor(t_max); + + // create interpolation_times vector with all days between day0 and day_max + std::vector tps(static_cast(day_max) - static_cast(day0) + 1); + std::iota(tps.begin(), tps.end(), day0); + + return interpolate_simulation_result(simulation_result, tps); +} + +template +TimeSeries interpolate_simulation_result(const TimeSeries& simulation_result, + const std::vector& interpolation_times) +{ + assert(simulation_result.get_num_time_points() > 0 && "TimeSeries must not be empty."); + + assert(std::is_sorted(interpolation_times.begin(), interpolation_times.end()) && + "Time points for interpolation have to be sorted in non-descending order."); + + if (interpolation_times.size() >= 2) { + assert((interpolation_times[1] > simulation_result.get_time(0) && + interpolation_times.rbegin()[1] <= simulation_result.get_last_time()) && + "All but the first and the last time point of interpolation have lie between simulation times (strictly " + "for lower boundary)."); + } + + TimeSeries interpolated(simulation_result.get_num_elements()); + + if (interpolation_times.size() == 0) { + return interpolated; + } + + size_t interp_idx = 0; + // add first time point of interpolation times in case it is smaller than the first time point of simulation_result + // this is used for the case that it equals the first time point of simulation up to tolerance + // this is necessary even if the tolerance is 0 due to the way the comparison in the loop is implemented (< and >=) + if (simulation_result.get_time(0) >= interpolation_times[0]) { + interpolated.add_time_point(interpolation_times[0], simulation_result[0]); + ++interp_idx; + } + + //interpolate between pair of time points that lie on either side of each interpolation point + for (Eigen::Index sim_idx = 0; + sim_idx < simulation_result.get_num_time_points() - 1 && interp_idx < interpolation_times.size();) { + //only go to next pair of time points if no time point is added. + //otherwise check the same time points again + //in case there is more than one interpolation point between the two time points + if (simulation_result.get_time(sim_idx) < interpolation_times[interp_idx] && + simulation_result.get_time(sim_idx + 1) >= interpolation_times[interp_idx]) { + interpolated.add_time_point( + interpolation_times[interp_idx], + linear_interpolation(interpolation_times[interp_idx], simulation_result.get_time(sim_idx), + simulation_result.get_time(sim_idx + 1), simulation_result[sim_idx], + simulation_result[sim_idx + 1])); + ++interp_idx; + } + else { + ++sim_idx; + } + } + + // add last time point of interpolation times in case it is larger than the last time point of simulation_result + // this is used for the case that it equals the last time point of simulation up to tolerance + if (interp_idx < interpolation_times.size() && + simulation_result.get_last_time() < interpolation_times[interp_idx]) { + interpolated.add_time_point(interpolation_times[interp_idx], simulation_result.get_last_value()); + } + + return interpolated; +} + +template +std::vector>> sum_nodes(const std::vector>>& ensemble_result) +{ + auto num_runs = ensemble_result.size(); + auto num_nodes = ensemble_result[0].size(); + auto num_time_points = ensemble_result[0][0].get_num_time_points(); + auto num_elements = ensemble_result[0][0].get_num_elements(); + + std::vector>> sum_result( + num_runs, std::vector>(1, TimeSeries::zero(num_time_points, num_elements))); + + for (size_t run = 0; run < num_runs; run++) { + for (Eigen::Index time = 0; time < num_time_points; time++) { + sum_result[run][0].get_time(time) = ensemble_result[run][0].get_time(time); + for (size_t node = 0; node < num_nodes; node++) { + sum_result[run][0][time] += ensemble_result[run][node][time]; + } + } + } + return sum_result; +} + +template +std::vector> ensemble_mean(const std::vector>>& ensemble_result) +{ + auto num_runs = ensemble_result.size(); + auto num_nodes = ensemble_result[0].size(); + auto num_time_points = ensemble_result[0][0].get_num_time_points(); + auto num_elements = ensemble_result[0][0].get_num_elements(); + + std::vector> mean(num_nodes, TimeSeries::zero(num_time_points, num_elements)); + + for (size_t run = 0; run < num_runs; run++) { + assert(ensemble_result[run].size() == num_nodes && "ensemble results not uniform."); + for (size_t node = 0; node < num_nodes; node++) { + assert(ensemble_result[run][node].get_num_time_points() == num_time_points && + "ensemble results not uniform."); + for (Eigen::Index time = 0; time < num_time_points; time++) { + assert(ensemble_result[run][node].get_num_elements() == num_elements && + "ensemble results not uniform."); + mean[node].get_time(time) = ensemble_result[run][node].get_time(time); + mean[node][time] += ensemble_result[run][node][time] / num_runs; + } + } + } + + return mean; +} + +template +std::vector> ensemble_percentile(const std::vector>>& ensemble_result, FP p) +{ + assert(p > 0.0 && p < 1.0 && "Invalid percentile value."); + + auto num_runs = ensemble_result.size(); + auto num_nodes = ensemble_result[0].size(); + auto num_time_points = ensemble_result[0][0].get_num_time_points(); + auto num_elements = ensemble_result[0][0].get_num_elements(); + + std::vector> percentile(num_nodes, TimeSeries::zero(num_time_points, num_elements)); + + std::vector single_element_ensemble(num_runs); //reused for each element + for (size_t node = 0; node < num_nodes; node++) { + for (Eigen::Index time = 0; time < num_time_points; time++) { + percentile[node].get_time(time) = ensemble_result[0][node].get_time(time); + for (Eigen::Index elem = 0; elem < num_elements; elem++) { + std::transform(ensemble_result.begin(), ensemble_result.end(), single_element_ensemble.begin(), + [=](auto& run) { + return run[node][time][elem]; + }); + std::sort(single_element_ensemble.begin(), single_element_ensemble.end()); + percentile[node][time][elem] = single_element_ensemble[static_cast(num_runs * p)]; + } + } + } + return percentile; +} + +template +FP result_distance_2norm(const std::vector>& result1, + const std::vector>& result2) +{ + using std::sqrt; + assert(result1.size() == result2.size()); + assert(result1.size() > 0); + assert(result1[0].get_num_time_points() > 0); + assert(result1[0].get_num_elements() > 0); + + auto norm_sqr = 0.0; + for (auto iter_node1 = result1.begin(), iter_node2 = result2.begin(); iter_node1 < result1.end(); + ++iter_node1, ++iter_node2) { + for (Eigen::Index time_idx = 0; time_idx < iter_node1->get_num_time_points(); ++time_idx) { + auto v1 = (*iter_node1)[time_idx]; + auto v2 = (*iter_node2)[time_idx]; + norm_sqr += ((v1 - v2).array() * (v1 - v2).array()).sum(); + } + } + return sqrt(norm_sqr); } /** From 9ddb52c20464ff00a0168943f0dc06f2b1058c7c Mon Sep 17 00:00:00 2001 From: julianlitz Date: Tue, 9 Sep 2025 11:04:28 +0000 Subject: [PATCH 3/3] Ad required fixes --- cpp/memilio/ad/ad.h | 34 +++++++++++ cpp/models/ode_secirvvs/parameters_io.h | 75 ++++++++++++++++--------- 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/cpp/memilio/ad/ad.h b/cpp/memilio/ad/ad.h index daa9e4a421..32e4e7dd13 100644 --- a/cpp/memilio/ad/ad.h +++ b/cpp/memilio/ad/ad.h @@ -61,6 +61,40 @@ static inline double round(const ad::internal::unary_intermediate +static inline double trunc(const ad::internal::active_type& x) +{ + return trunc(x._value()); +} +template +static inline double trunc(const ad::internal::binary_intermediate_aa& x) +{ + return trunc(x._value()); +} +template +static inline double trunc(const ad::internal::binary_intermediate_ap& x) +{ + return trunc(x._value()); +} +template +static inline double trunc(const ad::internal::binary_intermediate_pa& x) +{ + return trunc(x._value()); +} +template +static inline double trunc(const ad::internal::unary_intermediate& x) +{ + return trunc(x._value()); +} +} // namespace internal +} // namespace ad + // Allow std::numeric_limits to work with AD types. template struct std::numeric_limits> : public numeric_limits { diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index ded65eea53..d520a60099 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -228,6 +228,9 @@ IOResult read_confirmed_cases_data_fix_recovered(const std::vector const& vregion, Date date, std::vector>& vnum_rec, FP delay = 14.) { + using std::floor; + using std::trunc; + auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) { return a.date < b.date; }); @@ -255,7 +258,7 @@ IOResult read_confirmed_cases_data_fix_recovered(const std::vector(floor(trunc(FP(-delay)))))) { vnum_rec[region_idx][size_t(rki_entry.age_group)] = rki_entry.num_confirmed; } } @@ -352,15 +355,15 @@ IOResult set_confirmed_cases_data(std::vector& model, for (size_t group = 0; group < num_age_groups; group++) { t_Exposed[county].push_back( - static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); + static_cast(round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedNoSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedSevere[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedCritical[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); mu_C_R[county].push_back( model[county].parameters.template get>()[(AgeGroup)group]); @@ -376,7 +379,10 @@ IOResult set_confirmed_cases_data(std::vector& model, mu_C_R, mu_I_H, mu_H_U, scaling_factor_inf)); for (size_t county = 0; county < model.size(); county++) { - // if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) > 0) { + // if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + // [](const FP& a, const FP& b) { + // return evaluate_intermediate(a + b); + // }) > 0.0) { size_t num_groups = (size_t)model[county].parameters.get_num_groups(); for (size_t i = 0; i < num_groups; i++) { model[county].populations[{AgeGroup(i), InfectionState::ExposedNaive}] = num_Exposed[county][i]; @@ -406,7 +412,10 @@ IOResult set_confirmed_cases_data(std::vector& model, } // } - if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) == 0) { + if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) == 0.0) { log_warning( "No infections for unvaccinated reported on date {} for region {}. Population data has not been set.", date, region[county]); @@ -435,15 +444,15 @@ IOResult set_confirmed_cases_data(std::vector& model, for (size_t group = 0; group < num_age_groups; group++) { FP reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; t_Exposed[county].push_back( - static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); - t_InfectedNoSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); - t_InfectedSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + static_cast(round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); + t_InfectedNoSymptoms[county].push_back(static_cast(round( + FP(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t)))); + t_InfectedSymptoms[county].push_back(static_cast(round( + FP(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t)))); t_InfectedSevere[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedCritical[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); FP exp_fac_part_immune = model[county].parameters.template get>()[(AgeGroup)group]; @@ -475,7 +484,10 @@ IOResult set_confirmed_cases_data(std::vector& model, t_InfectedCritical, mu_C_R, mu_I_H, mu_H_U, scaling_factor_inf)); for (size_t county = 0; county < model.size(); county++) { - // if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) > 0) { + // if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + // [](const FP& a, const FP& b) { + // return evaluate_intermediate(a + b); + // }) > 0.0) { size_t num_groups = (size_t)model[county].parameters.get_num_groups(); for (size_t i = 0; i < num_groups; i++) { model[county].populations[{AgeGroup(i), InfectionState::ExposedPartialImmunity}] = num_Exposed[county][i]; @@ -494,7 +506,10 @@ IOResult set_confirmed_cases_data(std::vector& model, } } // } - if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) == 0) { + if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) == 0.0) { log_warning("No infections for partially vaccinated reported on date {} for region {}. " "Population data has not been set.", date, region[county]); @@ -523,15 +538,15 @@ IOResult set_confirmed_cases_data(std::vector& model, for (size_t group = 0; group < num_age_groups; group++) { FP reduc_t = model[0].parameters.template get>()[(AgeGroup)group]; t_Exposed[county].push_back( - static_cast(round(model[county].parameters.template get>()[(AgeGroup)group]))); - t_InfectedNoSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); - t_InfectedSymptoms[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t))); + static_cast(round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); + t_InfectedNoSymptoms[county].push_back(static_cast(round( + FP(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t)))); + t_InfectedSymptoms[county].push_back(static_cast(round( + FP(model[county].parameters.template get>()[(AgeGroup)group] * reduc_t)))); t_InfectedSevere[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); t_InfectedCritical[county].push_back(static_cast( - round(model[county].parameters.template get>()[(AgeGroup)group]))); + round(FP(model[county].parameters.template get>()[(AgeGroup)group])))); FP reduc_immune_exp = model[county].parameters.template get>()[(AgeGroup)group]; @@ -580,7 +595,10 @@ IOResult set_confirmed_cases_data(std::vector& model, num_icu[county][i]; } } - if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), 0.0) == 0) { + if (std::accumulate(num_InfectedSymptoms[county].begin(), num_InfectedSymptoms[county].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) == 0.0) { log_warning("No infections for vaccinated reported on date {} for region {}. " "Population data has not been set.", date, region[county]); @@ -673,10 +691,13 @@ IOResult set_population_data(std::vector& model, const std::vector< auto num_age_groups = ConfirmedCasesDataEntry::age_group_names.size(); std::vector> vnum_rec(model.size(), std::vector(num_age_groups, 0.0)); - BOOST_OUTCOME_TRY(read_confirmed_cases_data_fix_recovered(case_data, vregion, date, vnum_rec, 14.)); + BOOST_OUTCOME_TRY(read_confirmed_cases_data_fix_recovered(case_data, vregion, date, vnum_rec, 14.)); for (size_t region = 0; region < vregion.size(); region++) { - if (std::accumulate(num_population[region].begin(), num_population[region].end(), 0.0) > 0) { + if (std::accumulate(num_population[region].begin(), num_population[region].end(), FP(0.0), + [](const FP& a, const FP& b) { + return evaluate_intermediate(a + b); + }) > 0.0) { auto num_groups = model[region].parameters.get_num_groups(); for (auto i = AgeGroup(0); i < num_groups; i++) {